Skip to content
Docs · Quickstart

Add sign‑in in ten lines

Host one JSON file, point any OpenID Connect library at happauth.com, and place the button. There is no form, no secret and no approval.

The ten lines

With openid-client 6. Lines 2–3 find hApp Auth, 4–7 send the person to it, 8–10 turn the code into the person. callbackUrl is the URL hApp Auth sent the person back to, and the verifier lives in their session between the two requests.

sign-in.ts
import * as oidc from "openid-client";
const config = await oidc.discovery(new URL("https://happauth.com"),
  "https://yoursite.example/oauth/client.json", undefined, oidc.None());
const verifier = oidc.randomPKCECodeVerifier();
const signInUrl = oidc.buildAuthorizationUrl(config, { scope: "openid profile",
  redirect_uri: "https://yoursite.example/callback", code_challenge_method: "S256",
  code_challenge: await oidc.calculatePKCECodeChallenge(verifier) });
const tokens = await oidc.authorizationCodeGrant(config, callbackUrl,
  { pkceCodeVerifier: verifier });
const { sub } = tokens.claims()!;
  1. Host your client metadata

    Your client id is the HTTPS URL of a small JSON document on your own site (a client ID metadata document). hApp Auth fetches it when a person first signs in; there is nothing to register.

    https://yoursite.example/oauth/client.json
    {
      "client_id": "https://yoursite.example/oauth/client.json",
      "client_name": "Your site",
      "client_uri": "https://yoursite.example",
      "redirect_uris": ["https://yoursite.example/callback"],
      "grant_types": ["authorization_code"],
      "response_types": ["code"],
      "token_endpoint_auth_method": "none"
    }
    

    client_id must be the document’s own URL. Keep redirect URIs on the same origin; a redirect to localhost works for development and shows the person a warning line. The document is fetched with a 5 KB cap, no redirects and a short timeout, and cached by its HTTP headers.

  2. Point the library at the issuer

    The issuer is https://happauth.com. Discovery, keys and endpoints come from its metadata:

    Terminal
    curl https://happauth.com/.well-known/openid-configuration
    

    The same flow, spread over the two routes a server needs:

    server.ts
    import * as oidc from "openid-client";
    
    const SITE = "https://yoursite.example";
    const config = await oidc.discovery(
      new URL("https://happauth.com"),   // the issuer
      `${SITE}/oauth/client.json`,        // your client id: a URL you host
      undefined,
      oidc.None(),                       // a public client: no secret
    );
    
    // GET /sign-in — send the person to hApp Auth.
    export async function signIn(session: Session) {
      const verifier = oidc.randomPKCECodeVerifier();
      session.set("pkce", verifier);     // keep it until the callback
      return Response.redirect(oidc.buildAuthorizationUrl(config, {
        redirect_uri: `${SITE}/callback`,
        scope: "openid profile email",
        code_challenge: await oidc.calculatePKCECodeChallenge(verifier),
        code_challenge_method: "S256",
      }));
    }
    
    // GET /callback — the code becomes a verified person.
    export async function callback(request: Request, session: Session) {
      const tokens = await oidc.authorizationCodeGrant(config, new URL(request.url), {
        pkceCodeVerifier: session.take("pkce"),
      });
      const claims = tokens.claims()!;   // sub, acr, amr, auth_time, and what you asked for
      session.set("user", claims.sub);
      return Response.redirect(SITE);
    }
    
  3. Place the button

    Link it to your /sign-in route. Use it as it is: recognition is what lets a person notice a fake.

    Two variants (Emerald on light grounds; White on dark grounds and beside other providers), one height (44 px), English and Turkish. Coming: the 2 KB web component and the SVG set

What comes back

The ID token always carries sub and auth_time; acr and amr, which say how the person signed in, are coming. Ask for more with scopes; the person sees one line for each and can turn the optional ones off. Refusing an optional claim still signs them in, so read what you received, not what you asked for.

ScopeClaimsThe person sees
openidsub, auth_timeYour ID for this site (required)
profilename, pictureYour name and picture
emailemail, email_verifiedYour email address
github Cominggithub: { id, login, created_at }Your GitHub account
offline_accessA refresh tokenStay signed in while you’re away

More scopes, the assurance values and the subject rules are on OpenID Connect for Holochain.

Rules the provider keeps

  • Authorization code with PKCE (S256) for every client. No implicit grant, no password grant.
  • Codes work once and expire in ten minutes (60 seconds is coming). Every authorization response carries iss; openid-client checks it for you.
  • Access tokens are JWTs that live 5 minutes and name one audience. Refresh tokens rotate, and reusing an old one revokes the family.
  • A third-party site gets a pairwise sub: the same person has a different id on every site.