How sign-in works
The authorization code flow with PKCE as the SDK runs it, step by step, with every party and every artefact named and what is checked where.
Signing in with UNET is the OpenID Connect authorization code flow with PKCE. The SDK runs all of it for you; this page explains what happens so you can debug it, and so you know what is safe to trust.
The parties
| Party | What it is | What it does |
|---|---|---|
| Your app | The page or script that uses the SDK | Starts the sign-in, finishes it on the return page, keeps the session |
| The SDK | @unet/id-web, running in your app | Makes the requests, keeps the secrets of the flow, checks what comes back |
| UNET ID | The OpenID provider on the UNET API (https://api.unet.ovh/oidc by default) | Describes itself (discovery), hands out codes and tokens, answers for the profile |
| The sign-in page | A page on unet.social | Signs the person in (password, authenticator code, security key or passkey) and asks them to allow your app |
| The person's browser | Where all of it is seen | Carries the person between your app and unet.social |
Your app never sees a password, a key or an authenticator code. It only receives a one-time code, and trades it for tokens.
The sequence
| Step | Who | What happens |
|---|---|---|
| 1 | SDK | Reads the provider's discovery document and checks it (see below). It is cached for an hour. |
| 2 | SDK | Creates a random state and a random PKCE verifier, computes the challenge, and saves state, verifier and your returnTo as the pending sign-in. |
| 3 | Browser | Is redirected to the authorization endpoint with your App ID, return URL, scopes, state and challenge. |
| 4 | Sign-in page | Signs the person in. If they are already signed in to UNET in this browser, the page skips the sign-in form and shows who they are signed in as. |
| 5 | Sign-in page | Shows what your app asks for. The person allows or denies. (Skipped for UNET's own apps.) |
| 6 | Browser | Is redirected back to your return URL with a one-time code, your state, and the issuer in iss. |
| 7 | SDK | Checks the pending sign-in against what came back (state, age, iss), then sends code and verifier to the token endpoint. |
| 8 | UNET ID | Checks that the verifier matches the challenge from step 3 and returns an access token, usually a refresh token, and an ID token. |
| 9 | SDK | Saves the tokens, asks the userinfo endpoint for the profile with the access token, and resolves handleRedirectCallback() with the user. |
In code, steps 1 to 3 are signIn() and steps 7 to 9 are handleRedirectCallback(). Steps 4 to 6 happen on unet.social. See the quickstart for the code and the sign-in guide for the options.
What each piece is
| Artefact | What it is | Where it lives | Lifetime |
|---|---|---|---|
state | A random value that ties the answer to your request | Pending sign-in, in the SDK's storage | Until the callback, at most 15 minutes |
verifier | A random secret (32 bytes) that only the SDK knows | Pending sign-in | Same |
challenge | The SHA-256 of the verifier, sent in step 3 | In the redirect address only | One sign-in |
code | A one-time proof that the person allowed your app | The return URL | Used once; the server keeps it for 10 minutes at most |
| Access token | What authorizes calls on the person's behalf | The SDK's storage | One hour |
| Refresh token | Gets a new access token without asking the person again | The SDK's storage | See Tokens |
| ID token | A signed statement of who signed in, for your server | The SDK's storage | One hour |
| Discovery document | The addresses of the provider's endpoints | The SDK's storage (cache) | One hour |
The default storage is sessionStorage; the SDK's keys start with unet-id: followed by your App ID. See Sessions and storage for the alternatives.
Why PKCE
A public app (a page or a mobile app) cannot keep a secret, so it has no client secret to prove who is exchanging the code. PKCE closes the gap: only the party that created the verifier can redeem the code, because the server compares it with the challenge it saw in step 3. A code stolen from the return URL is useless without the verifier, which never left the SDK's storage.
What is checked, and where
| Check | Where | What it stops |
|---|---|---|
The discovery document names the issuer you configured, and its authorization, token, userinfo and revocation endpoints are on that issuer's origin over https | SDK, step 1 (also for a cached copy) | A tampered or poisoned document sending the code and tokens elsewhere. A failure is discovery_invalid. |
The issuer you configured is https (or http on localhost) | SDK, constructor | Sending tokens over an unprotected connection. A failure is config. |
state matches and the pending sign-in is younger than 15 minutes | SDK, step 7 | A forged return, or a stale one. A failure is StateMismatch. |
iss equals the configured issuer, when the server sends it | SDK, step 7 | A response from a different provider. A failure is issuer_mismatch. |
The verifier matches the challenge | UNET ID, step 8 | A stolen code. |
| The return URL is exactly one you registered | UNET ID, step 3 | Sending the code to an address you do not control. |
| Your app asks only for permissions it registered | UNET ID, step 3 (new apps) | An app growing its access without the console. A failure is invalid_scope. |
Security keys and 2FA
The sign-in happens on unet.social, not in your app, so whatever the person uses there works for your app without any change: a password, an authenticator code, a security key or a passkey. Your app does not need to know which.
After sign-in
- The SDK refreshes the access token a minute before it expires, using the refresh token. See Tokens.
- If the person removes your app in their settings, the next refresh fails and the session ends with
SessionExpired. - If your server needs to know who signed in, it must verify what the browser sends. See Verify on your server.