Post

Replies

Boosts

Views

Activity

Sign in with Apple for webapp
Please someone help me.... I have been struggling for quite a while now configuring everything for the flow of using Apple SSI for my web app. I have finally managed to configure a nginx reverse-proxy for development experience. Creating and working correctly with all the values from Apple Developer Console which involves the identifiers, keys and Id's. My issue is now, that everything works for my signin flow. SO when I sign in using my AppleID which is also connected to the developer account I get signed in and Apple signin RESTAPI returns a JWT with my email. But when everyone else signs in with their AppleID's the returned JWT doesn't have the emails. And I know that Apple only gives the email first time user signs in - but that's is not the issue. Here is my code (using bun.js, Elysia, Arctic): import Bun from 'bun'; import { Apple, type AppleCredentials, type AppleTokens } from 'arctic'; import type { BaseAuthAccountInfo } from './type'; import { createPrivateKey } from 'crypto'; import { sign, decode } from 'jsonwebtoken'; const { APPLE_CLIENT_ID, APPLE_TEAM_ID, APPLE_KEY_ID, APPLE_CLIENT_SECRET, APPLE_CLIENT_SECRET_JWT, } = Bun.env; type AppleReponseJWTPayload = { iss: string; aud: string; exp: number; iat: number; sub: string; at_hash: string; email: string; email_verified: boolean; auth_time: number; nonce_supported: boolean; }; const credentials: AppleCredentials = { clientId: APPLE_CLIENT_ID!, teamId: APPLE_TEAM_ID!, keyId: APPLE_KEY_ID!, certificate: -----BEGIN PRIVATE KEY-----\n${APPLE_CLIENT_SECRET}\n-----END PRIVATE KEY-----, }; const apple = new Apple(credentials, 'https://intellioptima.com/api/v1/aus/auth/apple/callback'); const appleAuthUrl = async (state: string) => { const appleUrl = await apple.createAuthorizationURL(state); appleUrl.searchParams.set('response_mode', 'form_post'); appleUrl.searchParams.set('scope', 'email'); return appleUrl; }; const getAppleTokens = async (code: string) => { console.log('Authorization code:', code); const appleResponse = await apple.validateAuthorizationCode(code); console.log('Apple Response:', appleResponse); return appleResponse; }; const getAppleAccount = async (tokens: AppleTokens): Promise => { const token = generateJWTApple(); const response = await fetch('https://appleid.apple.com/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ client_id: credentials.clientId, client_secret: token, grant_type: 'refresh_token', refresh_token: tokens.refreshToken!, }).toString(), }); if (!response.ok) { throw new Error('Failed to fetch user info'); } const appleResponse = await response.json(); console.log('APPLE_RESPONSE', appleResponse); const decodedUser = decode(appleResponse.id_token) as AppleReponseJWTPayload; if (!decodedUser || !decodedUser.email) { throw new Error('The user does not have an email address.'); } return { id: decodedUser.sub as string, username: decodedUser.email.split('@')[0], email: decodedUser.email!, name: decodedUser.email.split('@')[0], emailVerified: decodedUser.email_verified ?? false, iconUrl: `https://robohash.org/${decodedUser.email.split('@')[0]}.png`, }; }; function generateJWTApple() { const MINUTE = 60; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; const MONTH = 30 * DAY; const tokenKey = `-----BEGIN PRIVATE KEY-----\n${APPLE_CLIENT_SECRET_JWT!.replace(/\\n/g, '\n')}\n-----END PRIVATE KEY-----`; const privateKey = createPrivateKey(tokenKey); const now = Math.ceil(Date.now() / 1000); const expires = now + MONTH * 3; const claims = { iss: APPLE_TEAM_ID, iat: now, exp: expires, aud: 'https://appleid.apple.com', sub: 'com.intellioptima.aichat', }; return sign(claims, privateKey, { header: { kid: APPLE_KEY_ID, alg: 'ES256', }, }); } export { apple, appleAuthUrl, getAppleAccount, getAppleTokens }; What could be the issue??? I really hope someone out there can provide me with some details on what is going on <33333
1
0
378
Aug ’24
Needed: Apple Sign-In JWT Not Returning Email for Users Except Using My AppleID
Hi everyone, I've been working on integrating Apple Sign-In with my web app and have hit a roadblock that I can't seem to resolve. I've successfully set up an Nginx reverse-proxy for development purposes enabling SSL/TLS to provide HTTPS. I have configured everything using the values from the Apple Developer Console, including identifiers, keys, and IDs. The sign-in flow works perfectly when I use my Apple ID (which is linked to my developer account). The Apple Sign-In REST API returns a JWT with my email, as expected. However, when other users sign in with their Apple IDs, the returned JWT doesn't include their email addresses. I am aware that Apple only provides the email on the first sign-in, but this doesn't seem to be the issue here. Below is the relevant code I'm using (Bun.js, Elysia, Arctic): import Bun from 'bun'; import { Apple, type AppleCredentials, type AppleTokens } from 'arctic'; import type { BaseAuthAccountInfo } from './type'; import { createPrivateKey } from 'crypto'; import { sign, decode } from 'jsonwebtoken'; const { APPLE_CLIENT_ID, APPLE_TEAM_ID, APPLE_KEY_ID, APPLE_CLIENT_SECRET, APPLE_CLIENT_SECRET_JWT, } = Bun.env; type AppleReponseJWTPayload = { iss: string; aud: string; exp: number; iat: number; sub: string; at_hash: string; email: string; email_verified: boolean; auth_time: number; nonce_supported: boolean; }; const credentials: AppleCredentials = { clientId: APPLE_CLIENT_ID!, teamId: APPLE_TEAM_ID!, keyId: APPLE_KEY_ID!, certificate: `-----BEGIN PRIVATE KEY-----\n${APPLE_CLIENT_SECRET}\n-----END PRIVATE KEY-----`, }; const apple = new Apple(credentials, 'https://intellioptima.com/api/v1/aus/auth/apple/callback'); const appleAuthUrl = async (state: string) => { const appleUrl = await apple.createAuthorizationURL(state); appleUrl.searchParams.set('response_mode', 'form_post'); appleUrl.searchParams.set('scope', 'email'); return appleUrl; }; const getAppleTokens = async (code: string) => { console.log('Authorization code:', code); const appleResponse = await apple.validateAuthorizationCode(code); console.log('Apple Response:', appleResponse); return appleResponse; }; const getAppleAccount = async (tokens: AppleTokens): Promise<BaseAuthAccountInfo> => { const token = generateJWTApple(); const response = await fetch('https://appleid.apple.com/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ client_id: credentials.clientId, client_secret: token, grant_type: 'refresh_token', refresh_token: tokens.refreshToken!, }).toString(), }); if (!response.ok) { throw new Error('Failed to fetch user info'); } const appleResponse = await response.json(); console.log('APPLE_RESPONSE', appleResponse); const decodedUser = decode(appleResponse.id_token) as AppleReponseJWTPayload; if (!decodedUser || !decodedUser.email) { throw new Error('The user does not have an email address.'); } return { id: decodedUser.sub as string, username: decodedUser.email.split('@')[0], email: decodedUser.email!, name: decodedUser.email.split('@')[0], emailVerified: decodedUser.email_verified ?? false, iconUrl: `https://robohash.org/${decodedUser.email.split('@')[0]}.png`, }; }; function generateJWTApple() { const MINUTE = 60; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; const MONTH = 30 * DAY; const tokenKey = `-----BEGIN PRIVATE KEY-----\n${APPLE_CLIENT_SECRET_JWT!.replace(/\\n/g, '\n')}\n-----END PRIVATE KEY-----`; const privateKey = createPrivateKey(tokenKey); const now = Math.ceil(Date.now() / 1000); const expires = now + MONTH * 3; const claims = { iss: APPLE_TEAM_ID, iat: now, exp: expires, aud: 'https://appleid.apple.com', sub: 'com.intellioptima.aichat', }; return sign(claims, privateKey, { header: { kid: APPLE_KEY_ID, alg: 'ES256', }, }); } export { apple, appleAuthUrl, getAppleAccount, getAppleTokens }; I would greatly appreciate any insights or suggestions on what might be going wrong. I'm at a loss, and any help would be invaluable! Thanks in advance! <3333
1
0
529
Aug ’24