Apple OAuth provider

Before starting make sure you have an paid apple dev account.

OAuth integration for Apple. Refer to Apple Docs:

Provider id is apple.

import { apple } from "@lucia-auth/oauth/providers";
import { auth } from "./lucia.js";

const appleAuth = apple(auth, configs);

apple()#

const apple: (
	auth: Auth,
	config: {
		clientId: string;
		redirectUri: string;
		teamId: string;
		keyId: string;
		certificate: string;
		scope?: string[];
		responseMode?: "query" | "form_post";
	}
) => AppleProvider;
Parameters#
nametypedescriptiondefault
authAuthLucia instance
config.clientIdstringApple service identifier
config.redirectUristringan authorized redirect URI
config.teamIdstringApple teamId
config.keyId stringApple private keyId
config.certificatestringp8 certificate as string See how
config.scopestring[]an array of scopes[]
config.responseMode"query" | "form_post"OIDC response mode - must be "form_post" when requesting scopes"query"
Returns#
typedescription
AppleProviderApple provider

Import certificate#

Example using Node.js:

import fs from "fs";
import path from "path";

const certificatePath = path.join(
	process.cwd(),
	process.env.APPLE_CERT_PATH ?? ""
);

const certificate = fs.readFileSync(certificatePath, "utf-8");

export const appleAuth = apple(auth, {
	teamId: process.env.APPLE_TEAM_ID ?? "",
	keyId: process.env.APPLE_KEY_ID ?? "",
	certificate: certificate,
	redirectUri: process.env.APPLE_REDIRECT_URI ?? "",
	clientId: process.env.APPLE_CLIENT_ID ?? ""
});

Requesting scopes#

When requesting scopes (email and name), the options.responseMode must be set to "form_post". Unlike the default "query" response mode, **Apple will send an application/x-www-form-urlencoded POST request. You can retrieve the code by parsing the search queries or the form data.

post("/login/apple/callback", async (request) => {
	const url = new URL(request.url)
	const code = url.searchParams.get("code");
	if (!isValidState(request, code)) {
		// ...
	}
	const appleUserAuth = await
	// ...
})

Apple will also include a user field only in the first response, where you can access the user’s name.

const url = new URL(request.url);
const userJSON = url.searchParams.get("user");
if (userJSON) {
	const user = JSON.parse(userJSON);
	const { firstName, lastName, email } = user;
}

Interfaces#

AppleAuth#

See OAuth2ProviderAuth.

// implements OAuth2ProviderAuth<AppleAuth<_Auth>>
interface AppleAuth<_Auth extends Auth> {
	getAuthorizationUrl: () => Promise<readonly [url: URL, state: string]>;
	validateCallback: (code: string) => Promise<AppleUserAuth<_Auth>>;
}
Generics#
nameextendsdefault
_AuthAuthAuth

AppleTokens#

type AppleTokens = {
	accessToken: string;
	refreshToken: string | null;
	accessTokenExpiresIn: number;
	idToken: string;
};

AppleUser#

type AppleUser = {
	email?: string;
	email_verified?: boolean;
	sub: string;
};

AppleUserAuth#

Extends ProviderUserAuth.

interface AppleUserAuth<_Auth extends Auth> extends ProviderUserAuth<_Auth> {
	appleUser: AppleUser;
	appleTokens: AppleTokens;
}
propertiestypedescription
appleUserAppleUserApple user
appleTokensAppleTokensAccess tokens etc
Generics#
nameextends
_AuthAuth