diff --git a/package.json b/package.json index db9de5f..21271ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "apple-signin", - "version": "1.0.6", + "version": "1.0.9", "description": "Node.JS wrapper around Sign In with Apple REST API", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/readme.md b/readme.md index a237708..689b43c 100644 --- a/readme.md +++ b/readme.md @@ -29,7 +29,7 @@ Start "Sign in with Apple" flow by redirecting user to the authorization URL. const appleSignin = require("apple-signin"); const options = { - clientId: "com.gotechmakers.auth.client", // identifier of Apple Service ID. + clientID: "com.gotechmakers.auth.client", // identifier of Apple Service ID. redirectUri: "http://localhost:3000/auth/apple/callback", state: "123", // optional, An unguessable random string. It is primarily used to protect against CSRF attacks. scope: "email" // optional, default value is "email". @@ -50,14 +50,14 @@ More detail can be found in [Apple docs](https://developer.apple.com/documentati ```javascript const clientSecret = appleSignin.getClientSecret({ - clientId: "com.gotechmakers.auth.client", // identifier of Apple Service ID. + clientID: "com.gotechmakers.auth.client", // identifier of Apple Service ID. teamId: "teamId", // Apple Developer Team ID. privateKeyPath: "/var/www/app/AuthKey_XXX.p8", // path to private key associated with your client ID. keyIdentifier: "XXX" // identifier of the private key. }); const options = { - clientId: "com.gotechmakers.auth.client", // identifier of Apple Service ID. + clientID: "com.gotechmakers.auth.client", // identifier of Apple Service ID. redirectUri: "http://localhost:3000/auth/apple/callback", // use the same value which you passed to authorisation URL. clientSecret: clientSecret }; @@ -82,7 +82,7 @@ Result of ```getAuthorizationToken``` command is a JSON object representing Appl ### 3. Verify token signature and get unique user's identifier ```javascript -appleSignin.verifyIdToken(tokenResponse.id_token).then(result => { +appleSignin.verifyIdToken(tokenResponse.id_token, clientID).then(result => { const userAppleId = result.sub; }).catch(error => { // Token is not verified @@ -93,14 +93,14 @@ appleSignin.verifyIdToken(tokenResponse.id_token).then(result => { ```javascript const clientSecret = appleSignin.getClientSecret({ - clientId: "com.gotechmakers.auth.client", // identifier of Apple Service ID. + clientID: "com.gotechmakers.auth.client", // identifier of Apple Service ID. teamId: "teamId", // Apple Developer Team ID. privateKeyPath: "/var/www/app/AuthKey_XXX.p8", // path to private key associated with your client ID. keyIdentifier: "XXX" // identifier of the private key. }); const options = { - clientId: "com.gotechmakers.auth.client", // identifier of Apple Service ID. + clientID: "com.gotechmakers.auth.client", // identifier of Apple Service ID. clientSecret: clientSecret }; diff --git a/source/index.js b/source/index.js index f50e549..2b52612 100644 --- a/source/index.js +++ b/source/index.js @@ -6,6 +6,7 @@ const request = require('request-promise-native'); const ENDPOINT_URL = 'https://appleid.apple.com'; const DEFAULT_SCOPE = 'email'; +const TOKEN_ISSUER = 'https://appleid.apple.com'; const getAuthorizationUrl = (options = {}) => { if (!options.clientID) throw Error('clientID is empty'); @@ -29,11 +30,11 @@ const getAuthorizationUrl = (options = {}) => { }; const getClientSecret = options => { - if (!options.clientID) throw Error('clientID is empty'); - if (!options.teamId) throw Error('teamId is empty'); - if (!options.keyIdentifier) throw Error('keyIdentifier is empty'); - if (!options.privateKeyPath) throw Error('privateKeyPath is empty'); - if (!fs.existsSync(options.privateKeyPath)) throw Error("Can't find private key"); + if (!options.clientID) throw new Error('clientID is empty'); + if (!options.teamId) throw new Error('teamId is empty'); + if (!options.keyIdentifier) throw new Error('keyIdentifier is empty'); + if (!options.privateKeyPath) throw new Error('privateKeyPath is empty'); + if (!fs.existsSync(options.privateKeyPath)) throw new Error("Can't find private key"); const timeNow = Math.floor(Date.now() / 1000); @@ -52,9 +53,9 @@ const getClientSecret = options => { }; const getAuthorizationToken = async (code, options) => { - if (!options.clientID) throw Error('clientID is empty'); - if (!options.redirectUri) throw Error('redirectUri is empty'); - if (!options.clientSecret) throw Error('clientSecret is empty'); + if (!options.clientID) throw new Error('clientID is empty'); + if (!options.redirectUri) throw new Error('redirectUri is empty'); + if (!options.clientSecret) throw new Error('clientSecret is empty'); const url = new URL(ENDPOINT_URL); url.pathname = '/auth/token'; @@ -72,8 +73,8 @@ const getAuthorizationToken = async (code, options) => { }; const refreshAuthorizationToken = async (refreshToken, options) => { - if (!options.clientID) throw Error('clientID is empty'); - if (!options.clientSecret) throw Error('clientSecret is empty'); + if (!options.clientID) throw new Error('clientID is empty'); + if (!options.clientSecret) throw new Error('clientSecret is empty'); const url = new URL(ENDPOINT_URL); url.pathname = '/auth/token'; @@ -101,9 +102,15 @@ const getApplePublicKey = async () => { return pubKey.exportKey(['public']); }; -const verifyIdToken = async idToken => { - const pubKey = await getApplePublicKey(); - return jwt.verify(idToken, pubKey, { algorithms: 'RS256' }); +const verifyIdToken = async (idToken, clientID) => { + const applePublicKey = await getApplePublicKey(); + const jwtClaims = jwt.verify(idToken, applePublicKey, { algorithms: 'RS256' }); + + if (jwtClaims.iss !== TOKEN_ISSUER) throw new Error('id token not issued by correct OpenID provider - expected: ' + TOKEN_ISSUER + ' | from: ' + jwtClaims.iss); + if (clientID !== undefined && jwtClaims.aud !== clientID) throw new Error('aud parameter does not include this client - is: ' + jwtClaims.aud + '| expected: ' + clientID); + if (jwtClaims.exp < (Date.now() / 1000)) throw new Error('id token has expired'); + + return jwtClaims; };