Instead of choosing a password, people using your app can sign in with an account they already have. JSONPad handles the OAuth flow and creates an identity for each person, so your app adds a button and a return page.
Six providers are supported: Google, GitHub, Apple, Microsoft, Discord and Facebook. Each identity group has its own provider settings, so a game and an admin tool can use different apps, or none at all.
You'll need an OAuth app of your own at each provider, and its client ID and secret. That part can't be avoided: the provider needs to know whose app is asking. The Google, GitHub, Apple, Microsoft, Discord and Facebook guides walk through it, and the dashboard shows you exactly what to paste where. (Apple needs a paid developer account; the others are free.)
code and a state in the URL (either directly, or through JSONPad: see callback modes).New identities are named after the provider and the account, e.g. google-104829..., with the provider's name as the display name. They have no password until they set one, and they can't be logged in to with a password. The created flag in the response tells your app whether someone is new, so it can show a welcome page or ask for a nickname.
https, except for localhost.The client secret is encrypted before it's stored, and is never returned by the API or shown in the dashboard again. The dashboard checks it with the provider as soon as you save it, so a mistyped secret is caught straight away rather than when someone tries to sign in.
Your app's API token needs the authenticate permission for the group, and register as well if signing in should be able to create identities. Nothing else changes: tokens that already allow people to register and log in need no new permissions.
Ask which providers are enabled, so a provider you turn on later needs no code change, then start a sign-in when someone clicks the button.
import JSONPad from '@basementuniverse/jsonpad-sdk';
const jsonpad = new JSONPad('<YOUR TOKEN>', 'players');
// Show a button for each provider you've set up
const providers = await jsonpad.fetchIdentityOAuthProviders();
for (const { provider, name } of providers) {
const button = document.createElement('button');
button.textContent = `Sign in with ${name}`;
button.onclick = () =>
jsonpad.startIdentityOAuth(provider, {
redirectUrl: 'https://myapp.com/auth/callback',
});
document.querySelector('#sign-in')!.append(button);
}On the return page, complete the sign-in. The SDK reads the parameters from the page's URL, sends them to JSONPad with the verifier it kept when the sign-in started, and removes them from the address bar afterwards.
import JSONPad, { JSONPadError } from '@basementuniverse/jsonpad-sdk';
const jsonpad = new JSONPad('<YOUR TOKEN>', 'players');
try {
// Reads the parameters from the page's URL, then tidies them away
const { identity, token, created } = await jsonpad.completeIdentityOAuth();
// The SDK is now logged in as this identity; keep the token if you want to
// stay logged in after a reload
localStorage.setItem('identity-token', token!);
location.href = created ? '/welcome' : '/';
} catch (error) {
if (error instanceof JSONPadError && error.code === 20018) {
// The person changed their mind at the provider
location.href = '/sign-in';
} else {
showMessage("Sorry, we couldn't sign you in. Please try again.");
}
}A sign-in can only be completed once, from the same browser and with the same API token that started it, and within 10 minutes. If someone shares the URL of their return page (or it ends up in a log), it's no use to anyone else.
Every provider has a list of URLs it's allowed to send people back to, and only your app's own pages or JSONPad can be in it. Each provider you set up uses one of two modes. Apple only works with the second, because it posts its result as a form rather than redirecting:
JSONPad's callback page doesn't do anything else: it isn't authenticated, it doesn't use up the sign-in, and it sends no referrer. Your app's code is the same in both modes.
An identity can sign in with a password, with several provider accounts, or with any mix of them. A logged-in identity links another account with the same start-and-return flow.
// On the account settings page: "Connect your Google account"
await jsonpad.linkSelfIdentityProvider('google', {
redirectUrl: 'https://myapp.com/settings/accounts',
});
// The return page finishes it the same way as signing in; `token` is
// undefined, because the identity is already logged in
const { identity } = await jsonpad.completeIdentityOAuth();
// Which accounts can this identity sign in with?
const accounts = await jsonpad.fetchSelfIdentityProviders();
// "Disconnect". An identity's last way of signing in can't be removed
await jsonpad.unlinkSelfIdentityProvider('google');Accounts are never linked automatically, not even when the email addresses match: an email address alone isn't proof that the same person owns both. If someone signs in with a provider account whose verified email address already belongs to another identity in the group, the sign-in fails with IDENTITY_EMAIL_IN_USE (20010). Tell them to log in the way they did before and connect the account from their settings page.
Unlinking an identity's last way of signing in is refused with IDENTITY_LAST_LOGIN_METHOD (20022), so nobody can lock themselves out. Offer to set a password first. (You can still unlink anything from the dashboard, as the account owner.)
A new identity only gets an email address if the provider says it has been verified: Google's email_verified, GitHub's primary verified address, Apple's (including its private relay addresses) or a Discord account that has confirmed its address. Microsoft and Facebook addresses are never trusted: Microsoft lets an administrator set any address on an account in their own tenant, and Facebook doesn't say whether it has checked one, so an identity created with either has no email address until it adds one. An unverified address is still shown to you on the identity's linked account in the dashboard, but it isn't the identity's own email address, so it can't be used to log in or to reset a password.
If the identity group requires an email address and the provider doesn't give a verified one, the sign-in fails with IDENTITY_EMAIL_REQUIRED (20023). Ask for an address in your app and set it on the identity instead.
Add your development URL, e.g. http://localhost:5173/auth/callback, to the group's redirect URLs. http is allowed for localhost only. In "through JSONPad" mode that's all you need; in "straight back to your app" mode, register the same URL with the provider as well. Apple never accepts localhost at its end, but its sign-ins come back through JSONPad, so a localhost return page still works.
Consider separate identity groups (and separate OAuth apps) for development and production, so a development client secret is never enough to sign in to your live app.
20015 IDENTITY_OAUTH_PROVIDER_NOT_CONFIGUREDThe provider isn't set up, or isn't enabled, for this identity group. Check the group's page in the dashboard, and that your app is asking for the right group.20016 IDENTITY_OAUTH_REDIRECT_NOT_ALLOWEDThe redirectUrl isn't one of the group's redirect URLs. They're compared exactly, so a missing slash or a different port counts as a different URL. The error lists the URLs that are allowed.20017 IDENTITY_OAUTH_STATE_INVALIDThe sign-in has expired (they take at most 10 minutes), has already been completed, or is being completed by a different browser or API token. Start again.20018 IDENTITY_OAUTH_CANCELLEDThe person decided not to sign in. Show your sign-in page again, not an error.20019 IDENTITY_OAUTH_PROVIDER_ERRORThe provider refused. The message says what to do: a rejected client ID or secret, a callback URL that isn't registered with the provider, or an expired code.20020 IDENTITY_OAUTH_NOT_REGISTEREDNobody is linked to that provider account, and your API token can't register identities. Add the register permission for the group, or tell them to sign up first.20021 IDENTITY_PROVIDER_ALREADY_LINKEDThat provider account is already linked to another identity, or this identity already has an account with this provider.Every code is listed on the errors page, and the provider guides have a table of the provider's own error messages.
2026-09-17