Git Product home page Git Product logo

apple-signin-auth's Introduction

apple-signin-auth

 Apple signin for Node.js.

Follow @ahmad_tokyo

Prerequisites

  1. You should be enrolled in Apple Developer Program.
  2. Please have a look at Apple documentation related to "Sign in with Apple" feature.
  3. You should create App ID and Service ID in your Apple Developer Account.
  4. 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';
// OR const appleSignin = require('apple-signin-auth');
// OR import { getAuthorizationUrl } from 'apple-signin-auth';

const options = {
  clientID: 'com.company.app', // Apple Client ID
  redirectUri: 'http://localhost:3000/auth/apple/callback',
  // OPTIONAL
  state: 'state', // optional, An unguessable random string. It is primarily used to protect against CSRF attacks.
  responseMode: 'query' | 'fragment' | 'form_post', // Force set to form_post if scope includes 'email'
  scope: 'email' // optional
};

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', // Apple Client ID
  teamID: 'teamID', // Apple Developer Team ID.
  privateKey: 'PRIVATE_KEY_STRING', // private key associated with your client ID. -- Or provide a `privateKeyPath` property instead.
  keyIdentifier: 'XXX', // identifier of the private key.
  // OPTIONAL
  expAfter: 15777000, // Unix time in seconds after which to expire the clientSecret JWT. Default is now+5 minutes.
});

const options = {
  clientID: 'com.company.app', // Apple Client ID
  redirectUri: 'http://localhost:3000/auth/apple/callback', // use the same value which you passed to authorisation URL.
  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', // A token used to access allowed data.
    token_type: 'Bearer', // It will always be Bearer.
    expires_in: 300, // The amount of time, in seconds, before the access token expires.
    refresh_token: 'REFRESH_TOKEN', // used to regenerate new access tokens. Store this token securely on your server.
    id_token: 'ID_TOKEN' // A JSON Web Token that contains the user’s identity information.
}

3. Verify token signature and get unique user's identifier

try {
  const { sub: userAppleId } = await appleSignin.verifyIdToken(tokenResponse.id_token, {
    // Optional Options for further verification - Full list can be found here https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback
    audience: 'com.company.app', // client id - can also be an array
    nonce: 'NONCE', // nonce // Check this note if coming from React Native AS RN automatically SHA256-hashes the nonce https://github.com/invertase/react-native-apple-authentication#nonce
    // If you want to handle expiration on your own, or if you want the expired tokens decoded
    ignoreExpiration: true, // default is false
  });
} catch (err) {
  // Token is not verified
  console.error(err);
}

4. Refresh access token after expiration

const clientSecret = appleSignin.getClientSecret({
  clientID: 'com.company.app', // Apple Client ID
  teamID: 'teamID', // Apple Developer Team ID.
  privateKey: 'PRIVATE_KEY_STRING', // private key associated with your client ID. -- Or provide a `privateKeyPath` property instead.
  keyIdentifier: 'XXXXXXXXXX', // identifier of the private key. - can be found here https://developer.apple.com/account/resources/authkeys/list
  // OPTIONAL
  expAfter: 15777000, // Duration after which to expire JWT
});

const options = {
  clientID: 'com.company.app', // Apple Client ID
  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', // Apple Client ID
  teamID: 'teamID', // Apple Developer Team ID.
  privateKey: 'PRIVATE_KEY_STRING', // private key associated with your client ID. -- Or provide a `privateKeyPath` property instead.
  keyIdentifier: 'XXXXXXXXXX', // identifier of the private key. - can be found here https://developer.apple.com/account/resources/authkeys/list
  // OPTIONAL
  expAfter: 15777000, // Duration after which to expire JWT
});

const options = {
  clientID: 'com.company.app', // Apple Client ID
  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', // Apple Client ID
  teamID: 'teamID', // Apple Developer Team ID.
  privateKey: 'PRIVATE_KEY_STRING', // private key associated with your client ID. -- Or provide a `privateKeyPath` property instead.
  keyIdentifier: 'XXXXXXXXXX', // identifier of the private key. - can be found here https://developer.apple.com/account/resources/authkeys/list
  // OPTIONAL
  expAfter: 15777000, // Duration after which to expire JWT
});

const options = {
  clientID: 'com.company.app', // Apple Client ID
  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,
      {
        // Optional Options for further verification - Full list can be found here https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback
        audience: 'com.company.app', // client id - can also be an array
      },
    );
    const {
      sub: userAppleId,
      type,
      email // Only provided for email events
    } = events;

    switch (type) {
      case 'email-disabled':
        // Email will no longer be forwarded to the user via the private relay service
        break;
      case 'email-enabled':
        // Email will be forwarded to the user again
        break;
      case 'consent-revoked':
        // The user has decided to stop using Apple ID with this application - log them out
        break;
      case 'account-delete':
        // The user has deleted their Apple ID
        break;
    }

    res.sendStatus(200);
} catch (e) {
  // Event token is not verified
  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.

Extra API functions

  • _setFetch: (fetchFn: function) => void - Sets the fetch function, defaults to node-fetch. eg: appleSigninAuth._setFetch(fetchWithProxy);

Extras

  • 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.

apple-signin-auth's People

Contributors

a-tokyo avatar dependabot[bot] avatar zicyapp avatar dauden avatar ian-playside avatar pubkey avatar breyed avatar nikonhub avatar nitinhsharma avatar wsmd avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.