### Complete Passport-Apple Application Setup Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md This example demonstrates a full Express application setup including session management, Passport initialization, and the Passport-Apple strategy configuration. It covers middleware, authentication routes, and callback handling. ```javascript const express = require('express'); const session = require('express-session'); const passport = require('passport'); const AppleStrategy = require('passport-apple'); const jwt = require('jsonwebtoken'); const app = express(); // Middleware app.use(express.urlencoded({ extended: true })); app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true })); app.use(passport.initialize()); app.use(passport.session()); // Configure Apple Strategy passport.use(new AppleStrategy({ clientID: process.env.APPLE_CLIENT_ID, teamID: process.env.APPLE_TEAM_ID, keyID: process.env.APPLE_KEY_ID, callbackURL: process.env.APPLE_CALLBACK_URL, privateKeyString: process.env.APPLE_PRIVATE_KEY, passReqToCallback: true }, async (req, accessToken, refreshToken, idToken, profile, done) => { try { const decodedToken = jwt.decode(idToken, { json: true }); let userData; if (req.body.user) { userData = JSON.parse(req.body.user); } const user = await User.findOrCreate({ appleId: decodedToken.sub, email: decodedToken.email, firstName: userData?.firstName, lastName: userData?.lastName }); done(null, user); } catch (error) { done(error); } })); // Serialize/deserialize passport.serializeUser((user, done) => { done(null, user.id); }); passport.deserializeUser((id, done) => { User.findById(id, (err, user) => { done(err, user); }); }); // Routes app.get('/auth/apple', passport.authenticate('apple')); app.post('/auth/apple/callback', express.urlencoded({ extended: true }), (req, res) => { const sp = new URLSearchParams(); Object.entries(req.body).forEach(([key, value]) => { sp.set(key, String(value)); }); res.redirect(`/auth/apple/callback?${sp.toString()}`); } ); app.get('/auth/apple/callback', passport.authenticate('apple', { successReturnToOrRedirect: '/dashboard', failureRedirect: '/login' }) ); app.listen(3000); ``` -------------------------------- ### StrategyOptions Example Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md An example of how to construct the options object for the AppleStrategy. Ensure all required fields like clientID, teamID, keyID, and callbackURL are provided. ```javascript const options = { clientID: 'com.myapp.service', teamID: 'ABC123DEFG', keyID: 'W1K123K456', callbackURL: 'https://myapp.example.com/auth/apple/callback', privateKeyString: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----', passReqToCallback: true }; ``` -------------------------------- ### Install passport-apple and body-parser Source: https://github.com/ananay/passport-apple/blob/master/README.md Install the necessary packages for passport-apple and body-parser using npm. ```bash npm install --save passport-apple ``` ```bash npm install --save body-parser ``` -------------------------------- ### AppleConfig Example Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md An example of the configuration object used for AppleClientSecret. This object must contain the client_id, team_id, and key_id. ```javascript const config = { client_id: 'com.myapp.service', team_id: 'ABC123DEFG', key_id: 'W1K123K456' }; ``` -------------------------------- ### VerifyCallback Example Implementation Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md An example implementation of the VerifyCallback function, demonstrating how to decode the ID token, find or create a user, and call the done callback. ```javascript async (req, accessToken, refreshToken, idToken, profile, done) => { try { const decodedToken = jwt.decode(idToken, { json: true }); const { sub, email, email_verified } = decodedToken; const user = await User.findOrCreate({ appleId: sub, email }); done(null, user); } catch (err) { done(err); } } ``` -------------------------------- ### Initializing AppleStrategy Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/strategy.md Example of initializing the AppleStrategy with required options and a verification callback. This snippet demonstrates how to configure the strategy for authentication and handle user data. ```javascript const passport = require('passport'); const AppleStrategy = require('passport-apple'); const jwt = require('jsonwebtoken'); passport.use(new AppleStrategy({ clientID: 'com.myapp.service', teamID: 'ABC123DEFG', keyID: 'W1K123K456', callbackURL: 'https://myapp.example.com/auth/apple/callback', privateKeyString: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----', passReqToCallback: true }, async (req, accessToken, refreshToken, idToken, profile, done) => { try { // Decode the ID token to extract user identity const decodedIdToken = jwt.decode(idToken, { json: true }); const { sub, email, email_verified } = decodedIdToken; // First-time login: user data is provided in the request body let firstName, lastName; if (typeof req.query['user'] === 'string') { const userData = JSON.parse(req.query['user']); firstName = userData.firstName; lastName = userData.lastName; } // Find or create user in database const user = await User.findOrCreate({ appleId: sub, email, firstName, lastName }); return done(null, user); } catch (err) { return done(err); } })); ``` -------------------------------- ### TokenResponse Example Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md An example of a typical response object from Apple's token endpoint. ```javascript { access_token: "a9ec6a1e4628c0e05f6...", id_token: "eyJraWQiOiJXMUtKM0QzTjQiLCJhbGciOiJSUzI1NiJ9...", expires_in: 3600, token_type: "Bearer" } ``` -------------------------------- ### AuthorizationParamsOptions Example Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md An example demonstrating how to use the AuthorizationParamsOptions to set a custom state value when calling `authorizationParams()`. ```javascript const params = strategy.authorizationParams({ state: 'custom-state-value' // Result will also include: // response_type: 'code id_token' // scope: 'name email' // response_mode: 'form_post' }); ``` -------------------------------- ### Basic Passport-Apple Setup Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/README.md Illustrates how to initialize and use the AppleStrategy within a Passport.js application. Ensure you have the necessary environment variables for private keys. ```javascript const passport = require('passport'); const AppleStrategy = require('passport-apple'); passport.use(new AppleStrategy({ clientID: 'com.myapp.service', teamID: 'ABC123DEFG', keyID: 'W1K123K456', callbackURL: 'https://myapp.com/auth/apple/callback', privateKeyString: process.env.APPLE_PRIVATE_KEY }, async (req, accessToken, refreshToken, idToken, profile, done) => { const decoded = require('jsonwebtoken').decode(idToken, { json: true }); const user = await User.findOrCreate({ appleId: decoded.sub }); done(null, user); })); ``` -------------------------------- ### Decode ID Token Example Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md Demonstrates how to decode an ID token JWT using the 'jsonwebtoken' library and log the resulting payload. Ensure the 'jsonwebtoken' library is installed. ```javascript const jwt = require('jsonwebtoken'); const decodedToken = jwt.decode(idToken, { json: true }); console.log(decodedToken); // { // iss: 'https://appleid.apple.com', // aud: 'com.myapp.service', // exp: 1623456789, // iat: 1623453189, // sub: 'abc123def456', // email: 'user@example.com', // email_verified: 'true', // is_private_email: 'false' // } ``` -------------------------------- ### AppleAuthError Examples Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md Illustrative examples of AppleAuthError messages. These can help in diagnosing problems related to private key loading or JWT signing failures. ```text "AppleAuth Error - Couldn't read your Private Key file: ENOENT: no such file or directory, open '/path/to/key.p8'" ``` ```text "AppleAuth Error – Error occurred while signing: error:0906D06C:PEM routines:PEM_read_bio:no start line" ``` -------------------------------- ### JWTClaims Example Object Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md An example of a populated JWTClaims object. Use this as a reference when constructing your client secret. ```javascript { iss: 'ABC123DEFG', iat: 1623453189, exp: 1639089589, aud: 'https://appleid.apple.com', sub: 'com.myapp.service' } ``` -------------------------------- ### Handle Apple GET Callback and Authentication Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/README.md Handles the GET request to the callback URL after successful authentication, redirecting the user to the appropriate page based on the outcome. ```javascript // Handle redirected GET with authentication app.get('/auth/apple/callback', passport.authenticate('apple', { successRedirect: '/', failureRedirect: '/login' }) ); ``` -------------------------------- ### Configure GET Callback Route for Authentication Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md Handles the redirected GET request after the POST callback. It uses Passport to authenticate the user and redirects to success or failure routes. Includes a custom error handler for specific authentication errors. ```javascript app.get('/auth/apple/callback', passport.authenticate('apple', { successReturnToOrRedirect: '/dashboard', failureRedirect: '/login' }), (err, req, res, next) => { // Error handler for auth failures if (err instanceof Error && (err.name === 'AuthorizationError' || err.name === 'TokenError')) { const errorParams = new URLSearchParams({ error: err.name }); if ('code' in err && typeof err.code === 'string') { errorParams.set('code', err.code); } res.redirect(`/login?${errorParams.toString()}`); return; } res.redirect('/login'); } ); ``` -------------------------------- ### Passport Apple Callback Route Example Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/strategy.md This example shows how to configure an Express route to handle the Apple OAuth2 callback using Passport. It specifies success and failure redirects and includes basic error handling. ```javascript app.get('/auth/apple/callback', passport.authenticate('apple', { successRedirect: '/dashboard', failureRedirect: '/login' }), (err, req, res, next) => { // Error handling for auth failures if (err) { console.error('Authentication failed:', err); } } ); ``` -------------------------------- ### Configure POST Callback Route for Apple Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md Handles the POST request from Apple's callback. It converts the POST body into URL query parameters and redirects to the GET endpoint for further processing. ```javascript app.post('/auth/apple/callback', express.urlencoded({ extended: true }), (req, res) => { // Convert POST body to query parameters const sp = new URLSearchParams(); Object.entries(req.body).forEach(([key, value]) => { sp.set(key, String(value)); }); // Redirect to GET endpoint res.redirect(`/auth/apple/callback?${sp.toString()}`); } ); ``` -------------------------------- ### Handle Apple Callback GET Request and Errors Source: https://github.com/ananay/passport-apple/blob/master/README.md Handles the GET request after the redirect from the POST callback, including success and failure redirects. Includes specific error handling for AuthorizationError and TokenError. ```javascript const failureRedirect = '/failure'; // Here we handle the GET request after the redirect from the POST callback above app.get('/apple/callback', passport.authenticate('apple', { successReturnToOrRedirect: '/success', failureRedirect, }), (err, _req, res, _next) => { // for some reason, `failureRedirect` doesn't receive certain errors, so we need an error handler here. if (err instanceof Error && (err.name === 'AuthorizationError' || err.name === 'TokenError')) { // Common errors: // - AuthorizationError { code: 'user_cancelled_authorize' } - When the user cancels the operation // - TokenError { code: 'invalid_grant' } - The code has already been used const sp = new URLSearchParams({ error: err.name }); if ('code' in err && typeof err.code === 'string') sp.set('code', err.code); res.redirect(`${failureRedirect}${sp.toString()}`); return; } // unknown err object res.redirect(failureRedirect); }); ``` -------------------------------- ### Passport-Apple Authentication Routes Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/REFERENCE_MAP.md Defines the routes for initiating Apple authentication and handling the callback. The callback route supports both POST and GET requests to manage the OAuth2 flow. ```javascript app.get('/auth/apple', passport.authenticate('apple')); ``` ```javascript app.post('/auth/apple/callback', express.urlencoded({ extended: true }), (req, res) => { const sp = new URLSearchParams(req.body); res.redirect(`/auth/apple/callback?${sp.toString()}`); } ); ``` ```javascript app.get('/auth/apple/callback', passport.authenticate('apple', { successReturnToOrRedirect: '/dashboard', failureRedirect: '/login' }), (err, req, res, next) => { if (err) { // Handle errors } } ); ``` -------------------------------- ### Access UserData Example Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md Shows how to access and parse user data from the request body within an Express.js authenticate middleware. The user data is expected as a JSON-encoded string in `req.body.user`. ```javascript // In authenticate middleware app.post('/auth/apple/callback', (req, res) => { const userDataJson = req.body.user; if (userDataJson) { const userData = JSON.parse(userDataJson); console.log(userData.firstName, userData.lastName); } res.redirect('/auth/apple/callback?' + new URLSearchParams(req.body)); }); ``` -------------------------------- ### Handle Apple Callback POST Request Source: https://github.com/ananay/passport-apple/blob/master/README.md Redirects the POST request from Apple's callback to a GET request to handle SameSite cookie policies in browsers. ```javascript // Apple is different in that they POST back to the callback. // Because of SameSite policies in browsers don't allow cookies to be included in external POST requests // we wouldn't be able to access our express session here, and thereby not authenticate the session. // Therefore we redirect the POST request to GET (with query parameters). // https://github.com/ananay/passport-apple/issues/51#issuecomment-2312651378 app.post('/apple/callback', express.urlencoded({ extended: true }), (req, res) => { const { body } = req; const sp = new URLSearchParams(); Object.entries(body).forEach(([key, value]) => sp.set(key, String(value))); res.redirect(`/v1/auth/apple/callback?${sp.toString()}`); }); ``` -------------------------------- ### Instantiate AppleStrategy Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/REFERENCE_MAP.md Use this constructor to create a new instance of the AppleStrategy. Provide your Apple Developer Portal credentials and a callback URL. You must supply either the private key location or the private key content. ```javascript new Strategy(options, verify) ``` -------------------------------- ### Instantiate AppleClientSecret with Private Key File Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/client-secret.md Create an instance of AppleClientSecret using a configuration object and the file path to your private key. Ensure the configuration includes client_id, team_id, and key_id. ```javascript const AppleClientSecret = require('passport-apple/src/token'); // Using a private key file const generator1 = new AppleClientSecret( { client_id: 'com.myapp.service', team_id: 'ABC123DEFG', key_id: 'W1K123K456' }, '/path/to/private/key.p8', null ); ``` -------------------------------- ### Load Private Key from File Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md Configure the private key by providing its file path. This is a secure method for managing sensitive credentials. ```javascript // Directly from filesystem { privateKeyLocation: '/secure/path/to/AuthKey_ABC123K456.p8' } ``` -------------------------------- ### Initiate Apple Login Route Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/README.md Sets up a route to initiate the Apple authentication flow by redirecting the user to Apple's authorization server. ```javascript // Initiate login app.get('/auth/apple', passport.authenticate('apple')); ``` -------------------------------- ### Instantiate AppleClientSecret with Private Key String Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/client-secret.md Create an instance of AppleClientSecret using a configuration object and the private key content as a string. This is an alternative to providing a file path for the private key. ```javascript const AppleClientSecret = require('passport-apple/src/token'); // Using a private key string const generator2 = new AppleClientSecret( { client_id: 'com.myapp.service', team_id: 'ABC123DEFG', key_id: 'W1K123K456' }, null, '-----BEGIN PRIVATE KEY-----\nMIGTMBA...\n-----END PRIVATE KEY-----' ); ``` -------------------------------- ### Initialize Apple Strategy Source: https://github.com/ananay/passport-apple/blob/master/README.md Initialize the AppleStrategy with client credentials, callback URL, and key information. This snippet demonstrates how to handle user data, including first-time login information and JWT decoding. ```javascript import AppleStrategy from 'passport-apple'; import jsonwebtoken from 'jsonwebtoken'; import assert from 'node:assert'; passport.use(new AppleStrategy({ clientID: '', teamID: '', callbackURL: 'https://redirectmeto.com/http://localhost:8080/auth/apple/callback', // redirectmeto.com is useful for local testing keyID: '', privateKeyString: '', passReqToCallback: true, }, async (req, accessToken, refreshToken, idToken, profile, cb) => { try { const decodedToken = jsonwebtoken.decode(idToken, { json: true }); assert(decodedToken != null); // ONLY on the first auth each user makes, `req.query.user` gets set - after that it will no longer be sent in subsequent logins. // In that case `req.query.user` is a JSON encoded string, which has the properties `firstName` and `lastName`. // Note: If you need to test first auth again, you can remove the app from "Sign in with Apple" here: https://appleid.apple.com/account/manage const firstTimeUser = typeof req.query['user'] === 'string' ? JSON.parse(req.query['user']) : undefined; // JWT token should contain email if authenticated const { sub, email, email_verified } = decodedToken; // TODO implement your own function check for whether `sub` exists in your database (or create a new user if it does not) const dbUser = await upsertUser({ sub, email, email_verified, firstTimeUser }); return cb(null, { id: dbUser.id }); } catch (err) { return cb(err); } })); ``` -------------------------------- ### Basic Passport-Apple Strategy Configuration with Private Key File Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md Configure the Apple strategy using a private key file path. Ensure `passReqToCallback` is set if you need the request object in your verify callback. ```javascript const AppleStrategy = require('passport-apple'); const passport = require('passport'); passport.use(new AppleStrategy({ clientID: 'com.myapp.service', teamID: 'ABC123DEFG', keyID: 'W1K123K456', callbackURL: 'https://myapp.example.com/auth/apple/callback', privateKeyLocation: '/path/to/AuthKey_ABC123.p8', passReqToCallback: true }, verifyCallback)); ``` -------------------------------- ### Configure Passport-Apple with Environment Variables Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md This snippet shows a common pattern for configuring the Apple Strategy using environment variables. Ensure all necessary environment variables are set before application startup. ```javascript // Typical pattern const config = { clientID: process.env.APPLE_CLIENT_ID, teamID: process.env.APPLE_TEAM_ID, keyID: process.env.APPLE_KEY_ID, callbackURL: process.env.APPLE_CALLBACK_URL, privateKeyString: process.env.APPLE_PRIVATE_KEY }; passport.use(new AppleStrategy(config, verifyCallback)); ``` -------------------------------- ### Import Passport-Apple Strategy Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/README.md Demonstrates the two common ways to import the AppleStrategy class from the passport-apple package. ```javascript const AppleStrategy = require('passport-apple'); ``` ```javascript const { Strategy } = require('passport-apple'); ``` -------------------------------- ### Passport-Apple Strategy Constructor Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/START_HERE.md This snippet shows the constructor for the AppleStrategy, outlining its required and optional parameters for setting up Apple authentication. ```APIDOC ## new Strategy(options, verifyCallback) ### Description Initializes a new instance of the AppleStrategy for Passport.js. ### Parameters #### Constructor Parameters - **clientID** (string) - Required - Services ID provided by Apple. - **teamID** (string) - Required - Team ID associated with your Apple Developer account. - **keyID** (string) - Required - Key ID for your private key. - **callbackURL** (string) - Required - The OAuth redirect URI registered with Apple. - **privateKeyString** (string) - Required - The content of your private key file (PKCS#8 .p8 format). - **passReqToCallback** (boolean) - Optional - If true, the request object will be passed to the verify callback. Defaults to true. ### Verify Callback - **verifyCallback** (function) - Function to verify the user and return their profile. - **(error, user, info)** - **error**: An error occurred, e.g., during token validation. - **user**: User profile object. - **info**: Additional information, e.g., messages or tokens. ``` -------------------------------- ### AppleStrategy Constructor Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/strategy.md Initializes a new instance of the AppleStrategy. This strategy handles the OAuth2 flow for Apple Sign In. ```APIDOC ## new Strategy(options, verify) ### Description Initializes a new instance of the AppleStrategy, which is a Passport.js authentication strategy for Apple Sign in. ### Parameters #### options (object) - Required Configuration options for the strategy. - **clientID** (string) - Required - Client ID (Services ID from Apple Developer Portal), e.g., `com.ananayarora.app` - **teamID** (string) - Required - Team ID for the Apple Developer Account (visible in top-right of Apple Developer Portal) - **keyID** (string) - Required - Identifier for the private key on Apple Developer Account page - **callbackURL** (string) - Required - OAuth Redirect URI where Apple redirects after authentication - **privateKeyLocation** (string) - Optional - File path to the private key file. Either this or `privateKeyString` must be provided - **privateKeyString** (string) - Optional - Private key content as a string. Either this or `privateKeyLocation` must be provided - **passReqToCallback** (boolean) - Optional - Default: `true`. When true, the HTTP request object is passed as the first parameter to the verify callback - **authorizationURL** (string) - Optional - Default: `https://appleid.apple.com/auth/authorize`. Apple's authorization endpoint URL - **tokenURL** (string) - Optional - Default: `https://appleid.apple.com/auth/token`. Apple's token exchange endpoint URL #### verify (function) - Required Verification callback function. Its signature depends on `passReqToCallback`. When `passReqToCallback: true`: `async (req, accessToken, refreshToken, idToken, profile, done) => { ... }` - `req` (object): Express request object - `accessToken` (string): Access token returned by Apple - `refreshToken` (string|null): Refresh token (may be null for non-first-time logins) - `idToken` (string): Encoded JWT token containing user identity information - `profile` (object): Currently unused (legacy parameter from OAuth2Strategy) - `done` (function): Callback to invoke with `done(err, user, info)` When `passReqToCallback: false`: `async (accessToken, refreshToken, idToken, profile, done) => { ... }` - All parameters except `req` are passed. ### Returns None. Initializes the strategy instance. ### Example Usage ```javascript const passport = require('passport'); const AppleStrategy = require('passport-apple'); const jwt = require('jsonwebtoken'); passport.use(new AppleStrategy({ clientID: 'com.myapp.service', teamID: 'ABC123DEFG', keyID: 'W1K123K456', callbackURL: 'https://myapp.example.com/auth/apple/callback', privateKeyString: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----', passReqToCallback: true }, async (req, accessToken, refreshToken, idToken, profile, done) => { try { // Decode the ID token to extract user identity const decodedIdToken = jwt.decode(idToken, { json: true }); const { sub, email, email_verified } = decodedIdToken; // First-time login: user data is provided in the request body let firstName, lastName; if (typeof req.query['user'] === 'string') { const userData = JSON.parse(req.query['user']); firstName = userData.firstName; lastName = userData.lastName; } // Find or create user in database const user = await User.findOrCreate({ appleId: sub, email, firstName, lastName }); return done(null, user); } catch (err) { return done(err); } })); ``` ``` -------------------------------- ### Instantiate AppleClientSecret Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/REFERENCE_MAP.md Use this constructor to create a new instance of AppleClientSecret for generating client secrets. Provide configuration details and either the private key file path or the private key content. ```javascript new AppleClientSecret(config, privateKeyLocation, privateKeyString) ``` -------------------------------- ### Add Apple Login Route Source: https://github.com/ananay/passport-apple/blob/master/README.md Define the initial route for initiating the Apple sign-in process, which redirects the user to Apple's authentication server. ```javascript // This is the initial request that gets the whole process started (and redirects to Apple's server) app.get('/apple', passport.authenticate('apple')); ``` -------------------------------- ### Load Private Key from Literal String Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md Provide the private key directly as a multi-line string literal. Ensure the string includes the BEGIN and END PRIVATE KEY markers. ```javascript { privateKeyString: `-----BEGIN PRIVATE KEY----- MIGTMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgI1XFM+3F6XZX+2XaLqVcGLpL ... -----END PRIVATE KEY-----` } ``` -------------------------------- ### Configure Authentication Initiation Route Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md Sets up the Express route to initiate the Apple Sign-In flow using Passport. This route should be linked from your application's sign-in button or link. ```javascript const express = require('express'); const passport = require('passport'); const app = express(); // Route to start Apple Sign in app.get('/auth/apple', passport.authenticate('apple') ); ``` -------------------------------- ### Passport Apple Strategy Constructor Parameters Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/START_HERE.md Defines the required and optional parameters for initializing the AppleStrategy constructor. Ensure all required fields like clientID, teamID, keyID, callbackURL, and privateKeyString are provided. ```javascript new Strategy({ clientID: string, // Required: Services ID teamID: string, // Required: Team ID keyID: string, // Required: Key ID callbackURL: string, // Required: OAuth redirect privateKeyString: string, // Required: Private key passReqToCallback: boolean // Optional: Default true }, verifyCallback) ``` -------------------------------- ### Catching Private Key File Read Errors Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/errors.md Handle errors that occur when the private key file cannot be read. Verify the file path and permissions. ```javascript const generator = new AppleClientSecret(config, '/path/to/key.p8', null); generator.generate() .catch(error => { if (error.includes("Couldn't read your Private Key file")) { console.error('Private key file error:', error); // Handle: verify file exists, check permissions return res.status(500).json({ error: 'Configuration error: cannot read private key' }); } }); ``` -------------------------------- ### DoneCallback Usage Patterns Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md Illustrates common patterns for using the DoneCallback to handle successful authentication, authentication with additional information, and authentication failures. ```javascript // Successful authentication done(null, user); // Successful authentication with additional info done(null, user, { message: 'First login' }); // Authentication failure done(new Error('User not found')); // Explicit rejection (without throwing) done(null, false, { message: 'Account disabled' }); ``` -------------------------------- ### Import AppleClientSecret Class Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/client-secret.md Import the AppleClientSecret class from the passport-apple library. This is the first step before instantiating the class. ```javascript const AppleClientSecret = require('passport-apple/src/token'); ``` -------------------------------- ### Error Handling with Async/Await (try/catch) Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/client-secret.md Handle potential errors during client secret generation using a try/catch block with async/await. ```javascript // Using async/await try { const clientSecret = await generator.generate(); } catch (error) { console.error('Generation failed:', error); } ``` -------------------------------- ### StrategyOptions Configuration Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/REFERENCE_MAP.md Defines the configuration options required for initializing the Passport-Apple strategy. Ensure all required fields like clientID, teamID, keyID, and callbackURL are provided. ```typescript { clientID: string; // Required: Services ID teamID: string; // Required: Team ID keyID: string; // Required: Key ID callbackURL: string; // Required: OAuth redirect URL privateKeyLocation?: string; // Optional: File path privateKeyString?: string; // Optional: Key content passReqToCallback?: boolean; // Default: true authorizationURL?: string; // Default: Apple's tokenURL?: string; // Default: Apple's } ``` -------------------------------- ### Passport-Apple Strategy Configuration with Private Key String Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md Configure the Apple strategy using a private key string, often sourced from environment variables. This is useful for managing secrets securely. Ensure `passReqToCallback` is set if you need the request object in your verify callback. ```javascript const AppleStrategy = require('passport-apple'); const passport = require('passport'); passport.use(new AppleStrategy({ clientID: process.env.APPLE_CLIENT_ID, teamID: process.env.APPLE_TEAM_ID, keyID: process.env.APPLE_KEY_ID, callbackURL: process.env.APPLE_CALLBACK_URL, privateKeyString: process.env.APPLE_PRIVATE_KEY, passReqToCallback: true }, verifyCallback)); ``` -------------------------------- ### Configure Session Serialization Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md Set up functions to serialize and deserialize user data for session management. The serializeUser function determines what data to store in the session, and deserializeUser retrieves the full user object based on that data. ```javascript const passport = require('passport'); // Serialize user to session passport.serializeUser((user, done) => { done(null, user.id); }); // Deserialize user from session passport.deserializeUser((id, done) => { User.findById(id, (err, user) => { done(err, user); }); }); ``` -------------------------------- ### Strategy.authenticate Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/REFERENCE_MAP.md Processes an authentication request and handles the callback from Apple. It merges query and body parameters, extracts user information for the first login, and delegates to the parent OAuth2Strategy. ```APIDOC ## Strategy.authenticate(req, options) ### Description Processes an authentication request and handles the callback from Apple. It merges query and body parameters, extracts user information for the first login, and delegates to the parent OAuth2Strategy. ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example None ### Response #### Success Response None #### Response Example None ``` -------------------------------- ### AppleClientSecret Constructor Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/client-secret.md Initializes an instance of AppleClientSecret to generate JWT client secrets. It requires Apple credentials and either a path to a private key file or the private key content as a string. ```APIDOC ## Constructor AppleClientSecret ### Description Initializes an instance of AppleClientSecret to generate JWT client secrets. It requires Apple credentials and either a path to a private key file or the private key content as a string. ### Parameters #### Path Parameters - **config** (object) - Required - Configuration object containing Apple credentials. - **config.client_id** (string) - Required - Client ID (Services ID from Apple Developer Portal). - **config.team_id** (string) - Required - Team ID from Apple Developer Account. - **config.key_id** (string) - Required - Key ID identifier for the private key. - **privateKeyLocation** (string) - Optional - File path to the private key file. Either this or `privateKeyString` must be provided. - **privateKeyString** (string) - Optional - Private key content as a string. Either this or `privateKeyLocation` must be provided. ### Returns Instance of AppleClientSecret with bound methods. ### Throws - Does not throw in constructor; errors occur during `generate()` call ### Example Usage ```javascript const AppleClientSecret = require('passport-apple/src/token'); // Using a private key file const generator1 = new AppleClientSecret( { client_id: 'com.myapp.service', team_id: 'ABC123DEFG', key_id: 'W1K123K456' }, '/path/to/private/key.p8', null ); // Using a private key string const generator2 = new AppleClientSecret( { client_id: 'com.myapp.service', team_id: 'ABC123DEFG', key_id: 'W1K123K456' }, null, '-----BEGIN PRIVATE KEY-----\nMIGTMBA...\n-----END PRIVATE KEY-----' ); ``` ``` -------------------------------- ### Load Private Key from Base64 Encoded String Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md Load the private key from a base64-encoded string, often stored in an environment variable. The key is decoded to UTF-8 before being used. ```javascript // For base64-encoded key in environment variable const privateKeyString = Buffer.from( process.env.APPLE_PRIVATE_KEY_BASE64, 'base64' ).toString('utf-8'); { privateKeyString: privateKeyString } ``` -------------------------------- ### Handle User Not Found or Account Disabled in Verify Callback Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/errors.md Explicitly reject authentication if the user is not found or their account is inactive, providing a message to the client. This allows for graceful handling of known non-error conditions. ```javascript async (req, accessToken, refreshToken, idToken, profile, done) => { try { const decodedToken = jwt.decode(idToken, { json: true }); const user = await User.findById(decodedToken.sub); if (!user) { // Explicit rejection without error return done(null, false, { message: 'User not found' }); } if (!user.isActive) { // Explicit rejection with message return done(null, false, { message: 'Account disabled' }); } done(null, user); } catch (error) { done(error); } } ``` -------------------------------- ### authenticate(req, options) Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/strategy.md Processes the incoming authentication request. Merges request body and query parameters to handle Apple's POST-based callback. ```APIDOC ## authenticate(req, options) ### Description Processes the incoming authentication request. Merges request body and query parameters to handle Apple's POST-based callback. ### Method authenticate ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **req** (object) - yes - Express request object containing query parameters and POST body - **options** (object) - no - Additional authentication options ### Description This method overrides the parent `OAuth2Strategy.prototype.authenticate` to handle Apple's unique callback mechanism. Apple POSTs to the callback URL with form-encoded data, including an optional `user` parameter containing user information on first login. The method: 1. Merges `req.query` and `req.body` parameters (since Apple POSTs the redirect parameters) 2. Extracts and parses the `user` field from `req.body` if present and stores it in `req.appleProfile` 3. Delegates to the parent strategy's authenticate method ### Request Example ```javascript // Typically invoked by Passport during route handling app.get('/auth/apple/callback', passport.authenticate('apple', { successRedirect: '/dashboard', failureRedirect: '/login' }), (err, req, res, next) => { // Error handling for auth failures if (err) { console.error('Authentication failed:', err); } } ); ``` ``` -------------------------------- ### AppleStrategy Class Methods Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/REFERENCE_MAP.md Documentation for the methods available on the AppleStrategy class, used for handling authentication requests. ```APIDOC ## Strategy Class Methods ### `Strategy()` #### Description Constructor for the `Strategy` class. ### `authenticate(req, options)` #### Description Process an authentication request. #### Parameters - **req** (object) - Required - The incoming request object. - **options** (object) - Optional - Authentication options. ### `authorizationParams(options)` #### Description Modify OAuth2 parameters for authorization. #### Parameters - **options** (object) - Required - Parameters to modify. ``` -------------------------------- ### Initial Login Request Flow Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/strategy.md Describes the sequence of events for an initial login request using the Passport-Apple strategy, leading to redirection to Apple's authorization page. ```text GET /auth/apple ↓ passport.authenticate('apple') ↓ Redirects to Apple ID authorization page with parameters: - client_id - redirect_uri - response_type=code id_token - scope=name email - response_mode=form_post - state=[random] ``` -------------------------------- ### Verify ID Token Claims and Signature Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/errors.md This snippet demonstrates how to decode an ID token, verify the presence of essential claims (sub, email), and highlights the importance of signature verification for production environments. It catches and logs verification errors, redirecting the user to a login page. ```javascript try { const decoded = jwt.decode(idToken, { json: true }); // Verify required claims exist if (!decoded || !decoded.sub || !decoded.email) { throw new Error('Missing required claims in ID token'); } // Note: jwt.decode() does NOT verify signature // For production, verify the signature: const verified = jwt.verify(idToken, publicKey, { algorithms: ['RS256'], aud: clientID }); } catch (error) { console.error('Token verification failed:', error); return res.redirect('/login?error=invalid_token'); } ``` -------------------------------- ### AppleClientSecret Class Methods Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/REFERENCE_MAP.md Documentation for the methods available on the AppleClientSecret class, used for generating JWT client secrets. ```APIDOC ## AppleClientSecret Class Methods ### `AppleClientSecret()` #### Description Constructor for the `AppleClientSecret` class. ### `generate()` #### Description Generate a JWT client secret. This method is asynchronous. #### Returns - (Promise) - A promise that resolves with the generated JWT client secret. ### `_generateToken()` #### Description Sign JWT claims. This method is asynchronous and considered internal. #### Returns - (Promise) - A promise that resolves with the signed JWT. ``` -------------------------------- ### Handle Apple POST Callback Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/README.md Processes the POST request from Apple containing authentication tokens. This route is necessary because Apple sends callback data as form-urlencoded. ```javascript // Handle Apple's POST callback app.post('/auth/apple/callback', express.urlencoded({ extended: true }), (req, res) => { const sp = new URLSearchParams(req.body); res.redirect(`/auth/apple/callback?${sp.toString()}`); }); ``` -------------------------------- ### Handle Database Error in Verify Callback Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/errors.md Catch exceptions during user lookup and pass them to Passport's done() function. This is useful when external services like databases might fail. ```javascript async (req, accessToken, refreshToken, idToken, profile, done) => { try { const decodedToken = jwt.decode(idToken, { json: true }); const user = await User.findById(decodedToken.sub); // If database is down, error is thrown here done(null, user); } catch (error) { // Pass error to Passport done(error); } } ``` -------------------------------- ### StrategyOptions Type Definition Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md Defines the structure for configuration options passed to the AppleStrategy constructor. Use this to configure client ID, team ID, key ID, callback URL, and other optional parameters. ```typescript type StrategyOptions = { clientID: string; teamID: string; keyID: string; callbackURL: string; privateKeyLocation?: string; privateKeyString?: string; passReqToCallback?: boolean; authorizationURL?: string; tokenURL?: string; }; ``` -------------------------------- ### Error Handling with Promises (.catch()) Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/api-reference/client-secret.md Handle potential errors during client secret generation using the .catch() method on the returned promise. ```javascript // Using .catch() generator.generate() .then(clientSecret => { console.log('Success:', clientSecret); }) .catch(error => { console.error('Generation failed:', error); }); ``` -------------------------------- ### User Data Structure Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/REFERENCE_MAP.md Details the user data structure, which may include first and last names available only during the initial login. ```typescript { firstName?: string; // First name (first login only) lastName?: string; // Last name (first login only) } ``` -------------------------------- ### VerifyCallback Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/types.md The verification callback function passed to the AppleStrategy constructor. It handles the user verification process after successful authentication with Apple. ```APIDOC ## VerifyCallback ### Description The verification callback function passed to the AppleStrategy constructor. It handles the user verification process after successful authentication with Apple. ### Type Signature (passReqToCallback: true) ```typescript type VerifyCallback = ( req: express.Request, accessToken: string, refreshToken: string | null, idToken: string, profile: unknown, done: DoneCallback ) => void | Promise; ``` ### Type Signature (passReqToCallback: false) ```typescript type VerifyCallback = ( accessToken: string, refreshToken: string | null, idToken: string, profile: unknown, done: DoneCallback ) => void | Promise; ``` ### Parameters #### Path Parameters - **req** (express.Request) - Required - HTTP request object (when `passReqToCallback: true`) - **accessToken** (string) - Required - Access token returned by Apple for API access - **refreshToken** (string | null) - Required - Refresh token to obtain new access tokens; null for returning users - **idToken** (string) - Required - Encoded JWT token containing user identity claims - **profile** (unknown) - Required - Legacy parameter from OAuth2Strategy; currently unused by Apple - **done** (DoneCallback) - Required - Callback function `(err, user, info) => void` ### Request Example ```javascript async (req, accessToken, refreshToken, idToken, profile, done) => { try { const decodedToken = jwt.decode(idToken, { json: true }); const { sub, email, email_verified } = decodedToken; const user = await User.findOrCreate({ appleId: sub, email }); done(null, user); } catch (err) { done(err); } } ``` ### Related Types - DoneCallback (from passport-oauth2) ``` -------------------------------- ### Configure body-parser with Express Source: https://github.com/ananay/passport-apple/blob/master/README.md Configure the body-parser middleware for Express applications to handle incoming request bodies. ```javascript const bodyParser = require("body-parser"); app.use(bodyParser.urlencoded({ extended: true })); ``` -------------------------------- ### Local Testing Redirect URL Source: https://github.com/ananay/passport-apple/blob/master/README.md Use this URL format with a service like redirectmeto.com to test locally when Apple requires HTTPS for redirect URLs. Ensure to remove it from your Apple developer console after testing. ```plaintext https://redirectmeto.com/http://localhost:8080/auth/apple/callback ``` -------------------------------- ### Passport-Apple Strategy Configuration without Request in Callback Source: https://github.com/ananay/passport-apple/blob/master/_autodocs/configuration.md Configure the Apple strategy with `passReqToCallback` set to false, meaning the HTTP request object will not be passed to the verify callback. This simplifies the callback signature when request details are not needed. ```javascript passport.use(new AppleStrategy({ clientID: 'com.myapp.service', teamID: 'ABC123DEFG', keyID: 'W1K123K456', callbackURL: 'https://myapp.example.com/auth/apple/callback', privateKeyString: privateKeyContent, passReqToCallback: false // Request not passed to callback }, (accessToken, refreshToken, idToken, profile, done) => { // Only 5 parameters, no req // ... })); ```