Models
Failure Tempest 1All modelsStatus
Research
OverviewAPI fusionEvaluation
Developers
DocumentationConsoleChangelog
Company
PricingEnterpriseAboutSecurityContact
Account
Sign inCreate account
Docs/Login with Failure ID
Failure ID

Login with Failure ID.

Let people sign in to your app with the account they already use for Failure Model Labs. Standard OAuth 2.0 authorization code flow with PKCE, plus OpenID Connect ID tokens — use our zero-dependency SDK or any OIDC library.

How it works

  1. Your app sends the user to https://failure.fail/oauth/authorize with a PKCE challenge.
  2. They sign in to Failure ID (password, passkey, authenticator or face check) and approve your app once.
  3. Failure ID redirects back with a one-time code, which your app exchanges — with the PKCE verifier — for tokens.
  4. You get an access token (1 hour), an ES256-signed ID token, and with offline_access a rotating refresh token (30 days).
PKCE with S256 is required for every client, public or confidential. Redirect URIs must match exactly. Codes are single-use and expire in 10 minutes.

1. Register your app

Open Console → OAuth apps and register the app with its exact redirect URIs (https://, or http://localhost for development; custom schemes work for native apps). Choose public for single-page and mobile apps — they never hold a secret — or confidential if a server makes the token call; the client secret is shown once.

2. Browser apps (SDK)

import { FailureID } from "https://failure.fail/sdk/failure-id.js";

const fid = new FailureID({
  clientId: "fid_…",
  redirectUri: location.origin + "/callback",
  scope: "openid profile email offline_access",
});

// on the redirect page
const done = await fid.handleRedirect();   // { user, returnTo } or null

if (!fid.isSignedIn()) fid.renderButton("#login"); // or fid.login()
else console.log(await fid.getUser());       // { sub, email, name }

// call your own API with the token (refreshed automatically)
await fid.fetch("/api/me");
await fid.logout();                              // revokes the session

The SDK stores tokens in localStorage by default; pass storage: "session" or "memory" to change that. It is plain ES modules with no dependencies — copy failure-id.js into your bundle if you prefer not to load it from our domain.

3. Server apps

The same file exports the low-level helpers and runs on Node 18+, Bun, Deno and Workers. A confidential web app:

import { createPkce, buildAuthorizeUrl, exchangeCode, verifyIdToken } from "./failure-id.js";

// GET /login
const pkce = await createPkce();
session.pkce = pkce.verifier; session.state = crypto.randomUUID();
res.redirect(buildAuthorizeUrl({ clientId, redirectUri, state: session.state, codeChallenge: pkce.challenge }));

// GET /callback?code&state
if (req.query.state !== session.state) throw new Error("state mismatch");
const t = await exchangeCode({ code: req.query.code, codeVerifier: session.pkce, clientId, clientSecret, redirectUri });
const claims = await verifyIdToken(t.id_token, { clientId });  // checks ES256 signature, iss, aud, exp
session.userId = claims.sub;

Any OIDC library

Failure ID publishes standard discovery, so libraries such as openid-client, Auth.js, oidc-client-ts or AppAuth configure themselves from the issuer:

SettingValue
Issuerhttps://failure.fail
Discovery/.well-known/openid-configuration
Authorize/oauth/authorize
Token/oauth/token (form or JSON; client_secret_post, client_secret_basic or none)
User info/oauth/userinfo
Revocation/oauth/revoke
JWKS/.well-known/jwks.json (ES256)

Scopes and claims

ScopeGives you
openidAn ID token with sub — a stable user id unique to Failure ID
profilename
emailemail, with email_verified: false — accounts are invite-only but addresses aren't mailbox-verified yet, so don't treat email as proof of ownership
offline_accessA refresh token. Each use returns a new one; reusing an old one revokes the whole session

Authorize parameters

Parameter
response_typecode
client_id, redirect_uriAs registered
code_challenge, code_challenge_methodRequired, S256
scope, state, nonceRecommended
promptlogin forces re-authentication, consent shows the consent screen again, none fails with consent_required instead of prompting

Errors

Authorization errors come back on your redirect URI as error (access_denied, invalid_request, consent_required, unsupported_response_type). Token errors are JSON: invalid_client (401), invalid_grant, unsupported_grant_type. The SDK throws FailureIDError with .code set to these.

For your users

People see your app's name and the scopes before they approve, and can disconnect it at any time in Security → Connected apps, which revokes every token you hold for them.