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<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 };
What could be the issue???
I really hope someone out there can provide me with some details on what is going on <33333