apple-signin-auth
Apple signin for Node.js.
Prerequisites
- You should be enrolled in Apple Developer Program.
- Please have a look at Apple documentation related to "Sign in with Apple" feature.
- You should create App ID and Service ID in your Apple Developer Account.
- You should generate private key for your Service ID in your Apple Developer Account.
Apple Signin Setup
Deatiled confuguration instructions can be found at blog post and Apple docs.
Installation
npm install --save apple-signin-auth
OR
yarn add apple-signin-auth
Usage
1. Get authorization URL
Start "Sign in with Apple" flow by redirecting user to the authorization URL.
import appleSignin from 'apple-signin-auth';
const options = {
clientID: 'com.company.app',
redirectUri: 'http://localhost:3000/auth/apple/callback',
state: 'state',
responseMode: 'query' | 'fragment' | 'form_post',
scope: 'email'
};
const authorizationUrl = appleSignin.getAuthorizationUrl(options);
Alternatively, you can use Sign In with Apple browser javascript library.
2. Get access token
2.1. Retrieve "code" query param from URL string when user is redirected to your site after successful sign in with Apple. Example:
http://localhost:3000/auth/apple/callback?code=somecode&state=123.
2.2. Exchange retrieved "code" to user's access token.
More detail can be found in Apple docs.
const clientSecret = appleSignin.getClientSecret({
clientID: 'com.company.app',
teamID: 'teamID',
privateKey: 'PRIVATE_KEY_STRING',
keyIdentifier: 'XXX',
expAfter: 15777000,
});
const options = {
clientID: 'com.company.app',
redirectUri: 'http://localhost:3000/auth/apple/callback',
clientSecret: clientSecret
};
try {
const tokenResponse = await appleSignin.getAuthorizationToken(code, options);
} catch (err) {
console.error(err);
}
Result of getAuthorizationToken
command is a JSON object representing Apple's TokenResponse:
{
access_token: 'ACCESS_TOKEN',
token_type: 'Bearer',
expires_in: 300,
refresh_token: 'REFRESH_TOKEN',
id_token: 'ID_TOKEN'
}
3. Verify token signature and get unique user's identifier
try {
const { sub: userAppleId } = await appleSignin.verifyIdToken(tokenResponse.id_token, {
audience: 'com.company.app',
nonce: 'NONCE',
ignoreExpiration: true,
});
} catch (err) {
console.error(err);
}
4. Refresh access token after expiration
const clientSecret = appleSignin.getClientSecret({
clientID: 'com.company.app',
teamID: 'teamID',
privateKey: 'PRIVATE_KEY_STRING',
keyIdentifier: 'XXXXXXXXXX',
expAfter: 15777000,
});
const options = {
clientID: 'com.company.app',
clientSecret
};
try {
const {
access_token
} = appleSignin.refreshAuthorizationToken(refreshToken, options);
} catch (err) {
console.error(err);
}
5. a, Revoke tokens with refresh_token
const clientSecret = appleSignin.getClientSecret({
clientID: 'com.company.app',
teamID: 'teamID',
privateKey: 'PRIVATE_KEY_STRING',
keyIdentifier: 'XXXXXXXXXX',
expAfter: 15777000,
});
const options = {
clientID: 'com.company.app',
clientSecret,
tokenTypeHint: 'refresh_token'
};
try {
await appleSignin.revokeAuthorizationToken(refreshToken, options);
} catch (err) {
console.error(err);
}
5. b, Revoke tokens with access_token
const clientSecret = appleSignin.getClientSecret({
clientID: 'com.company.app',
teamID: 'teamID',
privateKey: 'PRIVATE_KEY_STRING',
keyIdentifier: 'XXXXXXXXXX',
expAfter: 15777000,
});
const options = {
clientID: 'com.company.app',
clientSecret,
tokenTypeHint: 'access_token'
};
try {
await appleSignin.revokeAuthorizationToken(accessToken, options);
} catch (err) {
console.error(err);
}
Optional: Server-to-Server Notifications
Apple provides realtime server-to-server notifications of several user lifecycle
events:
email-disabled
: The user hides their email behind Apple's private email
relay, and has opted to stop having emails forwarded by the private relay
service.email-enabled
: The user hides their email behind Apple's private email
relay, and has opted to resume having emails forwarded by the private relay
service.consent-revoked
: The user has decided to stop using Apple ID with your
application, e.g. by disconnecting the application from Settings. This should
be treated as a sign-out out by the user.account-delete
: The user has asked Apple to permanently delete their Apple
ID. The user identifier is no longer valid.
Notifications are sent for each app group.
The notification is sent as a POST
request with a JSON body. The request body
contains a JWT, with the event description on the JWT payload.
{
"payload": "<server-to-server notification JWT>"
}
To receive these notifications, you must do the following steps.
1. Host the webhook
app.get("/apple-signin-webhook", async (req, res) => {
try {
const { events } = await appleSignin.verifyWebhookToken(
req.body.payload,
{
audience: 'com.company.app',
},
);
const {
sub: userAppleId,
type,
email
} = events;
switch (type) {
case 'email-disabled':
break;
case 'email-enabled':
break;
case 'consent-revoked':
break;
case 'account-delete':
break;
}
res.sendStatus(200);
} catch (e) {
console.error(err)
res.sendStatus(500);
});
Note:
- TLS 1.2 is required to receive notifications at the specified endpoint.
2. Configure the webhook URL in the Apple Developer console
2.1. Sign in to Apple Developer, go to "Certificates, Identifiers & Profiles",
and select the Primary App ID for your application.
2.2 Enable the "Sign in with Apple" capability (if not already enabled) and
click "Configure" (or "Edit").
2.3 Under "Server to Server Notification Endpoint", enter the fully-qualified
URL for your webhook, e.g. https://example.com/api/apple-signin-webhook
,
and save the changes.
Notes:
- A server-to-server webhook can only be configured for a Primary App ID.
- The Apple docs for this step are located here.
- _setFetch:
(fetchFn: function) => void
- Sets the fetch function, defaults to node-fetch. eg: appleSigninAuth._setFetch(fetchWithProxy);
- Handles apple public keys switching solving this issue https://forums.developer.apple.com/thread/129047
- Caches Apple's public keys and only refetches when needed
- ES6 (Can be imported using
import appleSigning from 'apple-signin-auth/src'
) - Flow and TypeScript Types
Related Projects
Helpful resources
Contributing
Pull requests are highly appreciated! For major changes, please open an issue first to discuss what you would like to change.