Apple
Supported scopes are email
and name
.
For usage, see OAuth 2.0 provider.
import { Apple } from "arctic";
import type { AppleCredentials } from "arctic";
const credentials: AppleCredentials = {
clientId,
teamId,
keyId,
certificate
};
const apple = new Apple(credentials, redirectURI);
const url: URL = await apple.createAuthorizationURL(state, {
// optional
scopes
});
const tokens: AppleTokens = await apple.validateAuthorizationCode(code);
const tokens: AppleRefreshedTokens = await apple.refreshAccessToken(refreshToken);
Get user profile
Parse the ID token. See ID token claims.
Requesting scopes
When requesting scopes, the response_mode
param must be set to form_post
. Unlike the default "query"
response mode, Apple will send an application/x-www-form-urlencoded POST request as the callback, and the user JSON object will be sent in the request body. This is only available the first time the user signs in. Make sure to set the state cookie with the SameSite=None
attribute.
import { generateState } from "arctic";
const state = generateState();
const url = await apple.createAuthorizationURL(state);
url.searchParams.set("response_mode", "query");
setCookie("state", state, {
secure: true, // set to false in localhost
path: "/",
httpOnly: true,
maxAge: 60 * 10, // 10 min
sameSite: "none" // IMPORTANT!
});
app.post("/login/apple/callback", async (request: Request) => {
const formData = await request.formData();
const userJSON = formData.get("user");
if (typeof userJSON === "string") {
const user = JSON.parse(userJSON);
const {
name: { firstName, lastName },
email
} = user;
}
});
If you have CSRF protection implemented, you must allow form submissions from Apple or disable CSRF protection for specific routes.
import { verifyRequestOrigin } from "lucia";
const originHeader = request.headers.get("Origin");
const hostHeader = request.headers.get("Host");
if (
!originHeader ||
!hostHeader ||
!verifyRequestOrigin(originHeader, [hostHeader, "appleid.apple.com"])
) {
return new Response(null, {
status: 403
});
}