Apple
OAuth 2.0 provider for Apple.
Also see the OAuth 2.0 guide.
Initialization
The PKCS#8 private key is an instance of Uint8Array
.
import * as arctic from "arctic";
const apple = new arctic.Apple(clientId, teamId, keyId, pkcs8PrivateKey, redirectURI);
Here is an example to extract the PKCS#8 key from the PEM certificate.
import * as encoding from "@oslojs/encoding";
const certificate = `-----BEGIN PRIVATE KEY-----
TmV2ZXIgZ29ubmEgZ2l2ZSB5b3UgdXANCk5ldmVyIGdvbm5hIGxldCB5b3UgZG93bg0KTmV2ZXIgZ29ubmEgcnVuIGFyb3VuZCBhbmQgZGVzZXJ0IHlvdQ0KTmV2ZXIgZ29ubmEgbWFrZSB5b3UgY3J5DQpOZXZlciBnb25uYSBzYXkgZ29vZGJ5ZQ0KTmV2ZXIgZ29ubmEgdGVsbCBhIGxpZSBhbmQgaHVydCB5b3U
-----END PRIVATE KEY-----`;
const privateKey = encoding.decodeBase64IgnorePadding(
certificate
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\r", "")
.replaceAll("\n", "")
.trim()
);
Create authorization URL
import * as arctic from "arctic";
const state = arctic.generateState();
const scopes = ["name", "email"];
const url = apple.createAuthorizationURL(state, scopes);
Requesting scopes
When requesting scopes, the response_mode
query parameter must be set to form_post
.
const url = apple.createAuthorizationURL(state, scopes);
url.searchParams.set("response_mode", "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.
Since this is a cross-origin form request, make sure to relax your CSRF protections, including setting SameSite
attribute of the state cookie to None
.
/callback?user=%7B%22name%22%3A%7B%22firstName%22%3A%22John%22%2C%22lastName%22%3A%22Doe%22%7D%2C%22email%22%3A%22john%40example.com%22%7D&state=STATE
{ "name": { "firstName": "John", "lastName": "Doe" }, "email": "[email protected]" }
Validate authorization code
validateAuthorizationCode()
will either return an OAuth2Tokens
, or throw one of OAuth2RequestError
, ArcticFetchError
, UnexpectedResponseError
, or UnexpectedErrorResponseBodyError
. The ID token will always be returned regardless of the scope. T access token and refresh token currently does not have any uses.
Arctic provides decodeIdToken()
for decoding the ID token's payload.
import * as arctic from "arctic";
try {
const tokens = await apple.validateAuthorizationCode(code);
const idToken = tokens.idToken();
} catch (e) {
if (e instanceof arctic.OAuth2RequestError) {
// Invalid authorization code, credentials, or redirect URI
const code = e.code;
// ...
}
if (e instanceof arctic.ArcticFetchError) {
// Failed to call `fetch()`
const cause = e.cause;
// ...
}
// Parse error
}