Repository: ananay/apple-auth
Branch: master
Commit: 207e5253508c
Files: 9
Total size: 22.4 KB
Directory structure:
gitextract_tqs2csng/
├── .gitignore
├── README.md
├── SETUP.md
├── eslint.config.mjs
├── package.json
└── src/
├── apple-auth.d.ts
├── apple-auth.js
├── apple-auth.js.flow
└── token.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
node_modules
.AppleDouble
.DS_Store
.npmrc
================================================
FILE: README.md
================================================
# Sign in with Apple for Node.js
<a href="https://twitter.com/intent/follow?screen_name=ananayarora"><img src="https://img.shields.io/twitter/follow/ananayarora.svg?label=Follow%20@ananayarora" alt="Follow @ananayarora"></img></a>
<a href="https://npmjs.com/package/apple-auth">
<img src="https://img.shields.io/npm/dt/apple-auth.svg"></img>
<img src="https://img.shields.io/npm/v/apple-auth.svg"></img>
</a>
</p>
An easy-to-use Node.js library for Signing in with Apple!
Now with support for fetching the name and email!
⚠️ Important note: Apple will only provide you with the name ONCE which is when the user taps "Sign in with Apple" on your app the first time. Keep in mind that you have to store this in your database at this time! For every login after that, Apple will provide you with a unique ID and the email that you can use to lookup the username in your database.
**Check out the passport version of this library here:**
https://github.com/ananay/passport-apple
https://npmjs.com/package/passport-apple
## Setup
Begin by installing the library:
```npm install apple-auth```
The configurations for Sign in with Apple are quite extensive so I've made an extensive SETUP.md file that you can read
https://github.com/ananay/apple-auth/blob/master/SETUP.md
## Example
I've created an example of how to use this library with Express! Check it out here:
https://github.com/ananay/apple-auth-example
**Example live on https://apple.ananay.dev**
## Usage
Initialize it using the following code:
```
const fs = require('fs');
const AppleAuth = require('apple-auth');
const config = fs.readFileSync("./config/config");
const auth = new AppleAuth(config, './config/AuthKey.p8');
```
Methods:
- ```auth.loginURL()``` - Creates the Login URL that your users will use to login to
- ```auth.accessToken(grantCode)``` - Gets the access token from the grant code received
- ```auth.refreshToken(refreshToken)``` - Gets the access token from a refresh token
## Troubleshooting
### `invalid_grant` when authorization code is generated by iOS App
Fix: If the authorizationCode was generated by your app, you should use your App ID as your clientId and not your service one. Discussion: https://github.com/ananay/apple-auth/issues/13
## Questions / Contributing
Feel free to open issues and pull requests. If you would like to be one of the core creators of this library, please reach out to me at i@ananayarora.com or message me on twitter @ananayarora!
<h4> Created with ❤️ by <a href="https://ananayarora.com">Ananay Arora</a></h4>
================================================
FILE: SETUP.md
================================================
# Table of contents
- Apple Developer Account Configurations
- <a href="https://github.com/ananay/apple-auth/blob/master/SETUP.md#create-a-new-app-id">Create a new App ID</a>
- <a href="https://github.com/ananay/apple-auth/blob/master/SETUP.md#create-a-services-id">Create a services ID</a>
- <a href="https://github.com/ananay/apple-auth/blob/master/SETUP.md#create-a-key">Create a key</a>
- <a href="https://github.com/ananay/apple-auth/blob/master/SETUP.md#configuring-the-library">Configuring the Library</a>
# Apple Developer Account configurations
## Create a new App ID

You need to create this even if you don't have an iOS or a Mac app

Scroll down to "Capabilities", and find "Sign in with Apple" and check it.

Hit continue and then register.
## Create a services ID

Fill out the details here, and click configure on "Sign in with Apple".

Add your domain that you'll use in the "Domains" section and the redirect url that you want to allow

Click Continue and Register.
Now, you need to verify this domain and in order to do that, click on the Service ID that you just created, again, and click configure on "Sign in with Apple". When you do that, you should be able to see that there is a download and a verify button.

## Create a key
Go to the "Keys" section in your Developer account and create one like this:

Click on configure on the "Sign in with Apple" option and make sure it is assigned to the correct App ID. Click continue and register. Now, click on Download and *MAKE SURE YOU KEEP THE FILE SAFE AND SECURE! YOU CANNOT REDOWNLOAD IT ONCE YOU HAVE ALREADY DOWNLOADED IT*
# Configuring the library
Make a folder called "config" and add two files:
- The private key file that you just downloaded
- A new file called ```config.json```

Inside of config.json, paste the following sample:
```
{
"client_id": "",
"team_id": "",
"redirect_uri": "",
"key_id": "",
"scope": ""
}
```
The ```scope```field is to set what information we want to gather from the user. We can set ```email``` and/or ```name```. This information is still not provided by Apple because this feature is still in Beta - but if you provide it the first time, when Apple finally releases it, your application will already have this permission provided. Otherwise the user has to revoke permissions to your application and log in again to be prompted for the new information request.
The ```client_id``` is actually called the "Service ID" that you will create in the 'Identifiers' section

The ```team_id``` is the 10 character code on the top left of the developer page next to your name.

The ```redirect_uri``` is the return url you added in the developer portal

The ```key_id``` is the identifier for the private key you generated

You can now save this as config.json in the config folder.
# Customizing the library
When building this project, it may be useful to enable a debugging mode. Enabling this mode will log all failed requests to the console, and the reject error message will also contain more specific information about the error. To enable this mode, pass in an object with the key ```debug``` set to ```true``` when initializing the library. For example:
```
let auth = new AppleAuth(
config,
SECRET_KEY,
TEXT_METHOD,
{
debug: true
}
);
```
================================================
FILE: eslint.config.mjs
================================================
import js from "@eslint/js";
import globals from "globals";
import { defineConfig } from "eslint/config";
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs}"], plugins: { js }, extends: ["js/recommended"] },
{ files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } },
{ files: ["**/*.{js,mjs,cjs}"], languageOptions: { globals: globals.browser } },
]);
================================================
FILE: package.json
================================================
{
"name": "@zebedee/apple-auth",
"version": "1.1.0",
"description": "Sign in with Apple for NodeJS",
"main": "src/apple-auth.js",
"types": "src/apple-auth.d.ts",
"scripts": {
"dev": "nodemon src/apple-auth.js",
"start": "node src/apple-auth.js",
"lint": "eslint"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ananay/apple-auth.git"
},
"keywords": [
"apple",
"sign",
"in",
"sso",
"auth",
"authentication"
],
"author": "Ananay Arora <i@ananayarora.com> (https://ananayarora.com)",
"license": "MIT",
"dependencies": {
"axios": "^1.6.0",
"express": "^4.17.1",
"jsonwebtoken": "^9.0.0"
},
"bugs": {
"url": "https://github.com/ananay/apple-auth/issues"
},
"homepage": "https://github.com/ananay/apple-auth#readme",
"devDependencies": {
"@eslint/js": "^9.26.0",
"eslint": "^9.26.0",
"globals": "^16.1.0"
}
}
================================================
FILE: src/apple-auth.d.ts
================================================
declare module "apple-auth" {
export interface AppleAuthConfig {
client_id: string;
team_id: string;
redirect_uri: string;
key_id: string;
scope: string;
}
export interface AppleClientSecret{
_config: AppleAuthConfig,
_privateKeyLocation: string,
_privateKeyMethod: string
generate(): Promise<string>;
}
// https://developer.apple.com/documentation/signinwithapplerestapi/tokenresponse
export interface AppleAuthAccessToken {
access_token: string;
expires_in: number;
id_token: string;
refresh_token: string;
token_type: "bearer";
}
// https://developer.apple.com/documentation/signinwithapplerestapi/errorresponse
export interface AppleAuthError {
error: "invalid_request" | "invalid_client" | "invalid_grant" | "unauthorized_client" | "unsupported_grant_type" | "invalid_scope";
}
export interface CustomConfig {
debug: boolean | undefined;
}
export default class AppleAuth {
constructor(config: AppleAuthConfig, privateKeyLocation: string, privateKeyMethod: string, customConfig?: CustomConfig)
public _state: string;
_tokenGenerator: AppleClientSecret;
loginURL(): string;
accessToken(code: string): Promise<AppleAuthAccessToken>;
refreshToken(code: string): Promise<AppleAuthAccessToken>;
revokeToken(unique_id: string): Promise<AppleAuthAccessToken>;
}
}
================================================
FILE: src/apple-auth.js
================================================
/**
* Apple Auth Library that implements the 'Sign in with Apple' in NodeJS.
* Official Documentation: https://developer.apple.com/sign-in-with-apple/
* @author: Ananay Arora <i@ananayarora.com>
*/
const axios = require('axios');
const AppleClientSecret = require("./token");
const crypto = require('crypto');
const qs = require('querystring');
const { Buffer } = require('node:buffer');
class AppleAuth {
/**
* Configure the parameters of the Apple Auth class
* @param {object} config Configuration options
* @param {string} config.client_id Client ID (also known as the Services ID
* in Apple's Developer Portal). Example: com.ananayarora.app
* @param {string} config.team_id Team ID for the Apple Developer Account
* found on top right corner of the developers page
* @param {string} config.redirect_uri The OAuth Redirect URI
* @param {string} config.key_id The identifier for the private key on the Apple
* @param {string} config.scope the scope of information you want to get from the user (user name and email)
* Developer Account page
* @param {string} privateKeyLocation Private Key Location / the key itself
* @param {string} privateKeyMethod Private Key Method (can be either 'file' or 'text')
* @param {object} customConfig Custom Configuration options
* @param {boolean} customConfig.debug Enable debug mode. This will print the verbose error messages returned by Apple's servers
*/
constructor(config, privateKey, privateKeyMethod, customConfig = {}) {
if (typeof config == 'object') {
if (Buffer.isBuffer(config)) {
this._config = JSON.parse(config.toString());
} else {
this._config = config;
}
} else {
this._config = JSON.parse(config);
}
if (typeof customConfig == 'object') {
if (Buffer.isBuffer(customConfig)) {
this._customConfig = JSON.parse(customConfig.toString());
} else {
this._customConfig = customConfig;
}
} else {
this._customConfig = JSON.parse(customConfig);
}
this._state = "";
this._tokenGenerator = new AppleClientSecret(this._config, privateKey, privateKeyMethod);
this.loginURL = this.loginURL.bind(this);
}
/**
* Return the state for the OAuth 2 process
* @returns {string} state –The state bytes in hex format
*/
get state() {
return this._state;
}
/**
* Generates the Login URL
* @returns {string} url –The Login URL
*/
loginURL() {
this._state = crypto.randomBytes(5).toString('hex');
return "https://appleid.apple.com/auth/authorize?" + qs.stringify({
response_type: "code id_token",
client_id: this._config.client_id,
redirect_uri: this._config.redirect_uri,
state: this._state,
scope: this._config.scope,
response_mode: "form_post"
});
}
/**
* Get the access token from the server
* based on the grant code
* @param {string} code
* @returns {Promise<object>} Access Token object
*/
accessToken(code) {
return new Promise(
(resolve, reject) => {
this._tokenGenerator.generate().then((token) => {
const payload = {
grant_type: 'authorization_code',
code,
redirect_uri: this._config.redirect_uri,
client_id: this._config.client_id,
client_secret: token,
};
axios({
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(payload),
url: 'https://appleid.apple.com/auth/token'
}).then((response) => {
resolve(response.data);
}).catch((error) => {
if (this._customConfig?.debug) {
console.error(error);
reject("AppleAuth Error - An error occurred while getting response from Apple's servers: " + error + " - " + error?.response?.data?.error_description);
}
// If customConfig.debug isn't set, output in this format.
reject(
`AppleAuth Error - An error occurred while getting response from Apple's servers:
${JSON.stringify(error)}`
);
});
}).catch((err) => {
reject(err);
});
}
);
}
/**
* Get the access token from the server
* based on the refresh token
* @param {string} refreshToken
* @returns {object} Access Token object
*/
refreshToken(refreshToken) {
return new Promise(
(resolve, reject) => {
this._tokenGenerator.generate().then((token) => {
const payload = {
grant_type: 'refresh_token',
refresh_token: refreshToken,
redirect_uri: this._config.redirect_uri,
client_id: this._config.client_id,
client_secret: token,
};
axios({
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(payload),
url: 'https://appleid.apple.com/auth/token'
}).then((response) => {
resolve(response.data);
}).catch((err) => {
if(this._customConfig?.debug) {
console.error(err);
reject("AppleAuth Error - An error occurred while getting response from Apple's servers: " + err + " - " + err?.response?.data?.error_description);
}
reject("AppleAuth Error - An error occurred while getting response from Apple's servers: " + err);
});
}).catch((err) => {
reject(err);
});
}
);
}
revokeToken(unique_id) {
return new Promise(
(resolve, reject) => {
this._tokenGenerator.generate().then((token) => {
const payload = {
token: unique_id,
redirect_uri: this._config.redirect_uri,
client_id: this._config.client_id,
client_secret: token,
token_type_hint: 'access_token'
};
axios({
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(payload),
url: 'https://appleid.apple.com/auth/revoke'
}).then((response) => {
resolve(response.data);
}).catch((err) => {
if(this._customConfig?.debug) {
console.error(err);
reject("AppleAuth Error - An error occurred while getting response from Apple's servers: " + err + " - " + err?.response?.data?.error_description);
}
reject("AppleAuth Error - An error occurred while getting response from Apple's servers: " + err);
});
}).catch((err) => {
reject(err);
});
}
);
}
}
module.exports = AppleAuth;
================================================
FILE: src/apple-auth.js.flow
================================================
export type AppleAuthConfig = {|
client_id: string,
team_id: string,
redirect_uri: string,
key_id: string,
|}
// https://developer.apple.com/documentation/signinwithapplerestapi/tokenresponse
export type AppleAuthAccessToken = {|
access_token: string,
expires_in: number,
id_token: string,
refresh_token: string,
token_type: "bearer",
|}
// https://developer.apple.com/documentation/signinwithapplerestapi/errorresponse
export type AppleAuthError = {|
error: "invalid_request" | "invalid_client" | "invalid_grant" | "unauthorized_client" | "unsupported_grant_type" | "invalid_scope",
|}
declare export default class AppleAuth {
constructor(config: AppleAuthConfig, privateKeyLocation: string): AppleAuth;
loginURL(): string;
accessToken(code: string): Promise<AppleAuthAccessToken>;
refreshToken(code: string): Promise<AppleAuthAccessToken>;
}
================================================
FILE: src/token.js
================================================
/**
* Generates a client secret for Apple auth
* using the private key.
* @author: Ananay Arora <i@ananayarora.com>
*/
const jwt = require('jsonwebtoken');
const fs = require('fs');
class AppleClientSecret {
/**
*
* @param {object} config
* @param {string} config.client_id
* @param {string} config.team_id
* @param {string} config.redirect_uri
* @param {string} config.key_id
* @param {string} privateKeyLocation
* @param {string} privateKeyMethod
*/
constructor(config, privateKeyLocation, privateKeyMethod) {
this._config = config;
this._privateKeyLocation = privateKeyLocation;
if (typeof privateKeyMethod == 'undefined') {
this._privateKeyMethod = 'file';
} else if (privateKeyMethod == 'text' || privateKeyMethod == 'file') {
this._privateKeyMethod = privateKeyMethod;
} else {
this._privateKeyMethod = privateKeyMethod;
}
this.generate = this.generate.bind(this);
this._generateToken = this._generateToken.bind(this);
}
/**
* Generates the JWT token
* @param {string} clientId
* @param {string} teamId
* @param {string} privateKey
* @param {int} expiration
* @returns {Promise<string>} token
*/
_generateToken(clientId, teamId, privateKey, exp, keyid) {
return new Promise(
(resolve, reject) => {
// Curate the claims
const claims = {
iss: teamId,
iat: Math.floor(Date.now() / 1000),
exp,
aud: 'https://appleid.apple.com',
sub: clientId,
};
// Sign the claims using the private key
jwt.sign(claims, privateKey, {
algorithm: 'ES256',
keyid
}, (err, token) => {
if (err) {
reject("AppleAuth Error – Error occurred while signing: " + err);
return;
}
resolve(token);
});
}
);
}
/**
* Reads the private key file calls
* the token generation method
* @returns {Promise<string>} token - The generated client secret
*/
generate() {
return new Promise(
(resolve, reject) => {
var that = this;
function generateToken() {
let exp = Math.floor(Date.now() / 1000) + ( 86400 * 180 ); // Make it expire within 6 months
that._generateToken(
that._config.client_id,
that._config.team_id,
that._privateKey,
exp,
that._config.key_id
).then((token) => {
resolve(token);
}).catch((err) => {
reject(err);
});
}
if (!that._privateKey) {
if (that._privateKeyMethod == 'file') {
fs.readFile(that._privateKeyLocation, (err, privateKey) => {
if (err) {
reject("AppleAuth Error - Couldn't read your Private Key file: " + err);
return;
}
that._privateKey = privateKey;
generateToken();
});
} else {
that._privateKey = that._privateKeyLocation;
process.nextTick(generateToken);
}
} else {
process.nextTick(generateToken);
}
}
);
}
}
module.exports = AppleClientSecret;
gitextract_tqs2csng/
├── .gitignore
├── README.md
├── SETUP.md
├── eslint.config.mjs
├── package.json
└── src/
├── apple-auth.d.ts
├── apple-auth.js
├── apple-auth.js.flow
└── token.js
SYMBOL INDEX (17 symbols across 3 files)
FILE: src/apple-auth.d.ts
type AppleAuthConfig (line 2) | interface AppleAuthConfig {
type AppleClientSecret (line 9) | interface AppleClientSecret{
type AppleAuthAccessToken (line 17) | interface AppleAuthAccessToken {
type AppleAuthError (line 25) | interface AppleAuthError {
type CustomConfig (line 28) | interface CustomConfig {
class AppleAuth (line 31) | class AppleAuth {
FILE: src/apple-auth.js
class AppleAuth (line 13) | class AppleAuth {
method constructor (line 32) | constructor(config, privateKey, privateKeyMethod, customConfig = {}) {
method state (line 61) | get state() {
method loginURL (line 70) | loginURL() {
method accessToken (line 89) | accessToken(code) {
method refreshToken (line 132) | refreshToken(refreshToken) {
method revokeToken (line 163) | revokeToken(unique_id) {
FILE: src/token.js
class AppleClientSecret (line 10) | class AppleClientSecret {
method constructor (line 22) | constructor(config, privateKeyLocation, privateKeyMethod) {
method _generateToken (line 44) | _generateToken(clientId, teamId, privateKey, exp, keyid) {
method generate (line 75) | generate() {
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (24K chars).
[
{
"path": ".gitignore",
"chars": 42,
"preview": "node_modules\n.AppleDouble\n.DS_Store\n.npmrc"
},
{
"path": "README.md",
"chars": 2554,
"preview": "# Sign in with Apple for Node.js\n\n<a href=\"https://twitter.com/intent/follow?screen_name=ananayarora\"><img src=\"https:"
},
{
"path": "SETUP.md",
"chars": 4744,
"preview": "# Table of contents\n- Apple Developer Account Configurations\n - <a href=\"https://github.com/ananay/apple-auth/blob/ma"
},
{
"path": "eslint.config.mjs",
"chars": 377,
"preview": "import js from \"@eslint/js\";\nimport globals from \"globals\";\nimport { defineConfig } from \"eslint/config\";\n\n\nexport defau"
},
{
"path": "package.json",
"chars": 932,
"preview": "{\n \"name\": \"@zebedee/apple-auth\",\n \"version\": \"1.1.0\",\n \"description\": \"Sign in with Apple for NodeJS\",\n \"main\": \"sr"
},
{
"path": "src/apple-auth.d.ts",
"chars": 1382,
"preview": "declare module \"apple-auth\" {\n export interface AppleAuthConfig {\n client_id: string;\n team_id: string;\n redir"
},
{
"path": "src/apple-auth.js",
"chars": 8066,
"preview": "/**\n * Apple Auth Library that implements the 'Sign in with Apple' in NodeJS. \n * Official Documentation: https://develo"
},
{
"path": "src/apple-auth.js.flow",
"chars": 875,
"preview": "export type AppleAuthConfig = {|\n client_id: string,\n team_id: string,\n redirect_uri: string,\n key_id: string,\n|}\n\n/"
},
{
"path": "src/token.js",
"chars": 3959,
"preview": "/**\n * Generates a client secret for Apple auth\n * using the private key.\n * @author: Ananay Arora <i@ananayarora.com>\n "
}
]
About this extraction
This page contains the full source code of the ananay/apple-auth GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (22.4 KB), approximately 5.4k tokens, and a symbol index with 17 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.