SSO
Apple
Add Sign in with Apple to VitNode. Create a Services ID and key, register the HTTPS return URL and wire up the Apple SSO adapter.
Add a Continue with Apple button to /login and /register. Visitors can share their real email or a private relay address, and both work with VitNode.
Before you begin
- A running VitNode app. This page covers only the Apple parts; SSO setup explains the shared steps and where the API's
.envand config live. - A paid Apple Developer Program membership. Free accounts cannot create Sign in with Apple keys.
- A public HTTPS domain for the site. Apple rejects
http://, IP addresses andlocalhostreturn URLs, so local development needs a tunnel (see Develop locally).
Set up Sign in with Apple
Create an App ID
In Certificates, Identifiers & Profiles, add an App IDs identifier (type App), give it a bundle ID such as com.example.forum, and tick Sign in with Apple under capabilities. This is the primary App ID the web sign-in groups under.
Create a Services ID
Add another identifier, this time of type Services IDs. Its identifier, for example com.example.forum.web, is the client ID VitNode sends to Apple. The description is what visitors see on Apple's consent screen.
Register the domain and return URL
Open the new Services ID, tick Sign in with Apple and press Configure. Choose the primary App ID, then add:
| Field | Value |
|---|---|
| Domains | your-domain.com |
| Return URLs | https://your-domain.com/login/sso/apple |
VitNode builds the return URL from VITNODE_WEB_URL as {VITNODE_WEB_URL}/login/sso/apple, so they must match exactly: same scheme and host, no trailing slash.
Create a key
Under Keys, add a key, tick Sign in with Apple, configure it with the same primary App ID and register it. Download the .p8 file and note the 10-character Key ID. Apple lets you download the file once. Your 10-character Team ID is under Membership details in your developer account.
Set the environment variables
Keep these server-side and out of clientEnv. Paste the .p8 contents with \n for line breaks; VitNode turns them back into newlines.
APPLE_CLIENT_ID=com.example.forum.web
APPLE_TEAM_ID=A1B2C3D4E5
APPLE_KEY_ID=F6G7H8I9J0
APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIGT...\n-----END PRIVATE KEY-----"
VITNODE_WEB_URL=https://your-domain.comApple's token endpoint wants a client secret: a short ES256 JWT signed with the key. AppleSSOApiPlugin creates it for you, keeps it for 24 hours and renews it before it expires, so there is no secret to rotate by hand.
Add the adapter
Add AppleSSOApiPlugin to authorization.ssoAdapters in the API's vitnode.api.config.ts (where it lives), next to any adapters you already have. Keep the rest of the config as it is:
import { AppleSSOApiPlugin } from '@vitnode/core/api/adapters/sso/apple'
import { buildApiConfig } from '@vitnode/core/vitnode.config'
export const vitNodeApiConfig = buildApiConfig({
// ...
authorization: {
ssoAdapters: [
// ...
AppleSSOApiPlugin({
clientId: process.env.APPLE_CLIENT_ID,
teamId: process.env.APPLE_TEAM_ID,
keyId: process.env.APPLE_KEY_ID,
privateKey: process.env.APPLE_PRIVATE_KEY,
}),
],
},
})No authorization block yet? Create one with just ssoAdapters, as in the SSO setup. Then restart the dev server, since .env and the API config are only read at start-up.
Check the result
Open /login on your HTTPS domain and press the Apple button under Continue with. Apple asks whether to share or hide the email, then returns to /login/sso/apple and you land signed in.
How Apple's callback works
Because VitNode asks for the name and email, Apple returns with a cross-site POST (response_mode=form_post), and browsers do not send VitNode's SameSite=Lax sign-in cookie with it. VitNode's request middleware (part of createVitNodeStart) answers that POST with a 303 redirect to the same URL as a normal GET, which carries the cookie. Nothing is relaxed. The state is still checked against the browser that started the sign-in. Only code, state, error and the name from Apple's user field are forwarded.
Apple sends the name only on the first authorization. VitNode uses it as the new account's display name (or "Apple user" when it is hidden). A later import from Connected accounts finds no name, because Apple does not send it again.
Develop locally
Apple will not redirect to localhost. Expose the dev server through an HTTPS tunnel (for example Cloudflare Tunnel or ngrok) and use that host everywhere:
VITNODE_WEB_URL=https://forum-dev.example.com
VITNODE_API_URL=https://forum-dev.example.comAdd forum-dev.example.com to the Services ID's domains and https://forum-dev.example.com/login/sso/apple to its return URLs.
Scopes and profile fields
| Setting | Value |
|---|---|
| Scopes | name email |
| Account ID | ID token sub (stable per developer team) |
| Email trusted | when the ID token says email_verified: true |
| Profile fields | first name, last name (sent on the first authorization only) |
| ID token | RS256 signature, issuer, audience, expiry and nonce validated |
Private relay addresses (...@privaterelay.appleid.com) are real, verified inboxes. To email them, register your sending domain under Sign in with Apple for Email Communication in the developer account.
Troubleshooting
The Services ID, Team ID, Key ID or key do not belong together. Check that the
key has Sign in with Apple enabled for the same primary App ID as the Services
ID, and that APPLE_CLIENT_ID is the Services ID, not the App ID.
- "Invalid redirect_uri" on Apple's page. The return URL is not listed on the Services ID, or
VITNODE_WEB_URLdiffers from it. - The callback page says the request expired. The sign-in started on another host than it returned to (for example
localhostvs. the tunnel), so the browser had no matching cookie. - A missing variable. The button still renders. A missing
APPLE_CLIENT_IDfails as soon as it is pressed; a missing team ID, key ID or private key fails only after Apple's screen, when VitNode exchanges the code. Either way the server log showsMissing Apple ....