Skip to content

Sign in with Apple

Your users can sign in with their Apple Account instead of choosing a name and seed phrase. It works like Sign in with Google: each user gets their own key, the same one every time they sign in, on any device.

  • No PrefsDB account for you or your users. You need an Apple Developer account and a Services ID, the same setup any website uses for "Sign in with Apple".
  • Your app, your sign-in screen. Apple's sign-in shows your app's name, not PrefsDB's.
  • Nothing to remember or lose. There's no seed phrase; signing in again finds the same data.

Ready-made buttons

Sign-in Buttons adds this provider's button, and any of the others, to your page with a few lines, and takes care of the one-time codes and popups shown below. You still need the setup in step 1.

How it works

  1. Your page asks PrefsDB for a one-time code (PrefsDB.idTokenNonce()).
  2. When the user clicks, your page opens Apple's sign-in with Apple's own script, passing that code.
  3. The user signs in on Apple's screens. Apple hands your page a signed proof of who they are, which includes the code.
  4. Your page passes that proof to PrefsDB.getkeyWithIdToken() with provider: "apple". PrefsDB checks it with Apple's keys and gives you the user's key.

From there it's an ordinary PrefsDB object.

1. Set up Sign in with Apple

If your website already has "Sign in with Apple", use its Services ID and skip this step. You need a paid Apple Developer Program membership.

  1. Create an App ID. In Certificates, Identifiers & Profiles, open Identifiers and click +. Choose App IDs, then the type App, and click Continue. Enter a description and a bundle ID, such as com.example.app. Under Capabilities, tick Sign In with Apple, then click Continue and Register. If you already have an iOS or Mac app, you can use its App ID instead: open it, tick Sign In with Apple and save.
  2. Create a Services ID. Back on Identifiers, click + again and choose Services IDs. Enter:

    • Description: your app's name. Apple shows it to users when they sign in.
    • Identifier: something like com.example.web. It must be different from the App ID's bundle ID. This identifier is your Services ID: your page passes it to Apple as the client ID. It isn't a secret.

    Click Continue and Register. 3. Configure it. The Identifiers list shows App IDs at first; switch the menu at its top right to Services IDs and click the one you just made. Tick Sign In with Apple and click Configure: - Primary App ID: the App ID from step 1. - Domains and Subdomains: your site's host only, such as app.example.com (no https://, no path). - Return URLs: a page on your site, such as https://app.example.com/. With the popup used below, Apple doesn't actually open it, but it must be listed and must match what your page passes exactly. 4. Click Next and Done, then Continue and Save.

You don't need a private key or a client secret for this.

Your page must be served over https. Apple doesn't allow localhost here, so test on a real https address.

Finding your way around Apple's pages

  • No Certificates, Identifiers & Profiles? It needs a paid membership, and in a team only the Account Holder or an Admin can create identifiers.
  • Your Services ID isn't in the list. The list shows App IDs unless you switch the menu at its top right to Services IDs.
  • Which value to copy. The Services ID is the Identifier you typed (com.example.web), not its description and not your Team ID or the App ID.
  • More than one site. Add each site's host under Domains and Subdomains and a page on each under Return URLs, on the same Services ID. You can list several of each, separated by commas.
  • Changes can take a few minutes to reach Apple's sign-in page.

2. Add sign-in to your page

<script src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
<script src="https://prefsdb.com/prefsdb.v1.min.js"></script>

<button id="signin">Sign in with Apple</button>

<script>
// The popup has to open straight from the click, so get the
// one-time code ahead of time (each one lasts 10 minutes).
let nonce = null;
const getNonce = () => PrefsDB.idTokenNonce().then((n) => { nonce = n; });
getNonce();

document.getElementById("signin").addEventListener("click", async () => {
  if (!nonce) return;
  AppleID.auth.init({
    clientId: "com.example.web",              // your Services ID
    scope: "email",
    redirectURI: "https://app.example.com/",  // one of its Return URLs
    nonce,
    usePopup: true,
  });
  nonce = null;
  const signingIn = AppleID.auth.signIn();    // opens the popup now
  getNonce();                                 // a new code for next time
  try {
    const data = await signingIn;
    const db = await PrefsDB.getkeyWithIdToken(data.authorization.id_token, { provider: "apple", name: "settings" });
    await db.key("theme").write("dark");
    console.log(await db.key("theme").read());
  } catch (err) {
    console.error("Sign-in failed:", err.error, err.message);
  }
});
</script>
# 1. A one-time code, to pass to Apple as the nonce
curl -s https://prefsdb.com/api/v1/getkey/nonce/

# 2. The user signs in with Apple in a browser. Apple returns an
#    ID token containing the code; that can't be done with curl.

# 3. Trade the ID token for the user's key (in the request body, never the URL)
curl -s -c cookies.txt https://prefsdb.com/api/v1/getkey/ \
  --data-urlencode "id_token=$ID_TOKEN" \
  --data-urlencode "provider=apple" \
  --data-urlencode "name=settings"

A few things to know:

  • Each code works once, within 10 minutes, so get a new one after every sign-in, as the example does.
  • Popups need a click. Browsers allow a popup only right after one, which is why the example gets the code before the click rather than during it.
  • name picks one of several keys for the same user, as with getkey(). Leave it out and it's "default".
  • The same options as getkey() work here too: offline, offlineTtl, sessionTtl, optimistic, encryption and throwOnError. namespace and appcred don't apply.
  • To change the key itself, call db.rotate() with no arguments.
  • Apple's button design. Apple asks websites to show its own "Sign in with Apple" button style; see Apple's guidelines for the button.

Signing out

When your user signs out, call await db.logout(). It deletes what the key kept in this browser (cached values and changes not sent yet), so the next person on a shared computer can't see them. Then forget your db object. With the sign-in buttons, call signin.signOut() instead, which does the same. See Signing out.

Which users get which key

A key belongs to one Apple Account in your app: your Services ID and the name you pass. Another app gives the same person a different key.

Email address

Apple shares the user's email address only if you ask for it with the email scope, as the example does. Users can choose Hide My Email, and Apple then gives you a private relay address that forwards to them. When Apple confirms an address, PrefsDB keeps it with the key; without one, the key still works right away, with no email step.

Letting users see their data

db.reviewUrl() is a page where your users sign in with Apple again to see their data, download it all, or upgrade their storage.

Apple identifies each user differently to every developer, so this page signs users in with your Services ID, and you have to allow it once. In Certificates, Identifiers & Profiles, open your Services ID, click Configure next to Sign in with Apple, and add:

  • Domains and Subdomains: prefsdb.com
  • Return URLs: https://prefsdb.com/review/apple/callback.php

Keep your own domain and Return URL; list each value alongside them, separated by commas. Then click Next, Done, Continue and Save.

Until you do, Apple shows an error on its own page when a user opens the link. Apple's sign-in screen shows your app's name, as it does in your app. The page works only for keys made with a Services ID, not ones made in an iPhone or Mac app with its bundle ID.

Storage

A signed-in user's free storage is shared across your apps that use PrefsDB with Sign in with Apple. See Storage space, and use db.onStorage() to warn users before they run out.

Encrypting data in the browser

Client-side encryption works with signed-in users too, but it still needs its own key or passphrase. Don't make one from anything in the Apple sign-in, such as the user's ID or email: PrefsDB sees those, so data encrypted with them wouldn't be private from PrefsDB.

If something goes wrong

What you see What to do
popup_closed_by_user (from Apple's script) The user closed the window before signing in.
popup_blocked_by_browser (from Apple's script) The browser blocked the popup. Call AppleID.auth.signIn() directly in the click, as the example does.
invalid_client or invalid_request on Apple's page The Services ID, domain or Return URL doesn't match what's configured at Apple.
IDTOKEN_INVALID The sign-in couldn't be checked, or it was too old. Ask the user to sign in again (with a new idTokenNonce()).
IDTOKEN_REPLAYED That sign-in was already used. Get a new nonce and sign in again.
IDTOKEN_KEYS_UNAVAILABLE Sign-in is briefly unavailable. Try again shortly.

More in Error Codes.