Developers

Sign in with Unidy.

Let people share only what your website needs, with their permission. Standards-based identity with OpenID Connect. The authorization code flow with PKCE, a signed ID token, and a private identifier for each person on each website.

Issuer
https://id.unidy.com
Flow
Authorization code + PKCE (S256)
Subject
Pairwise: one per website
ID token
Signed, RS256 or ES256

How it works

Six steps of plain OpenID Connect. Nothing to learn that your library doesn't already know.

  1. 1Your app

    Send the person to Unidy

    With a PKCE challenge, a state and a nonce.

    GET /oauth2/auth
  2. 2id.unidy.com

    Unidy shows your request

    Your website's name, logo and web address, and each detail you ask for.

  3. 3The person

    Signs in and decides

    Unticks what they don't want to share. Then Allow, Allow once or Deny.

  4. 4Your app

    A code comes back

    To your registered redirect address, with your state.

    ?code=…&state=…
  5. 5Your server or page

    Exchange it for tokens

    Trade the code and the PKCE verifier for an ID token and an access token.

    POST /oauth2/token
  6. 6Your app

    Start your own session

    Check the ID token, look up sub in your records, and sign the person in.

Quick start

Everything below is standard. If you use an OpenID Connect library, point it at the discovery address and most of this is done for you.

  1. 1

    Register your website

    In the Unidy app, your company's approved Unidy business account opens Developers and adds a website: its name, web address, privacy policy and, if you like, a logo. Then the details (scopes) the website may ask for. A request for any other scope is refused.

    Choose once how it signs people in:

    • With a server: you get a client id and a secret, shown once. The server exchanges the code.
    • In the browser: a client id and no secret. PKCE with S256 is required, and only your redirect origins may call the token endpoint.
    • Redirect addresses: up to 10, all https on one host. For your own machine, register a second website with http://localhost addresses.
  2. 2

    Read the discovery document

    Read the endpoints from it rather than hard-coding them.

    curl https://id.unidy.com/.well-known/openid-configuration
    
    {
      "authorization_endpoint": "https://id.unidy.com/oauth2/auth",
      "token_endpoint": "https://id.unidy.com/oauth2/token",
      "userinfo_endpoint": "https://id.unidy.com/userinfo",
      "jwks_uri": "https://id.unidy.com/.well-known/jwks.json",
      …
    }
  3. 3

    Send the authorization request

    Make a random verifier and keep it, with a random state and nonce, until the person comes back. Send the SHA-256 challenge of the verifier.

    const base64url = (bytes) =>
      btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
    const random = () => base64url(crypto.getRandomValues(new Uint8Array(32)));
    
    const config = await (await fetch('https://id.unidy.com/.well-known/openid-configuration')).json();
    const verifier = random(), state = random(), nonce = random();
    const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
    
    const url = new URL(config.authorization_endpoint);
    url.search = new URLSearchParams({
      response_type: 'code',
      client_id: 'YOUR_CLIENT_ID',
      redirect_uri: 'https://shop.example/callback',
      scope: 'openid profile unidy_id email',
      state,
      nonce,
      code_challenge: base64url(new Uint8Array(digest)),
      code_challenge_method: 'S256',
    });
    location.assign(url); // keep verifier, state and nonce in sessionStorage first
  4. 4

    Exchange the code for tokens

    On your redirect page, check that state matches the one you sent, once. Then exchange the code. A server authenticates with its secret (HTTP Basic). A browser website sends client_id instead, with no secret.

    curl -X POST https://id.unidy.com/oauth2/token \
      -u 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' \
      -d grant_type=authorization_code \
      -d code=THE_CODE \
      -d redirect_uri=https://shop.example/callback \
      -d code_verifier=THE_VERIFIER
    
    { "access_token": "…", "id_token": "…", "token_type": "bearer", "expires_in": …, "scope": "openid profile unidy_id email" }
  5. 5

    Validate the ID token

    Never trust an ID token you haven't checked. Your OpenID Connect library does all of this:

    • The signature, with the key from jwks_uri that matches the token's kid.
    • iss equals the issuer in the discovery document.
    • aud contains your client id.
    • exp is still in the future.
    • nonce equals the one you sent.
  6. 6

    Identify the person

    Key the account by sub. It's private to your website: the same person has a different sub everywhere else, so nobody can follow them across the web with it.

    Use unidy_id to show who signed in. It's the Unidy ID the person goes by, and they can personalise it, so it isn't a key.

    {
      "sub": "8b1c2f…e40f",
      "aud": ["YOUR_CLIENT_ID"],
      "nonce": "…",
      "name": "Ana Ruiz",
      "unidy_id": "ana.ruiz",
      "email": "ana.shop@mail.example",
      "email_verified": false
    }
  7. 7

    Create your own session

    Look sub up in your records. Unidy never tells you whether the person is new: if you have no account for that sub, create one. Then start your usual session.

  8. 8

    Optional: read the details with the access token

    GET /api/v1/me answers sub and each detail the person agreed to share and still allows. After Allow once, or if the person disconnects your website, it answers not_linked.

    curl https://id.unidy.com/api/v1/me \
      -H 'Authorization: Bearer ACCESS_TOKEN'
    
    { "sub": "8b1c2f…e40f", "name": "Ana Ruiz", "email": "ana.shop@mail.example", "email_verified": false }

Scopes and claims

openid is always asked. Each other scope is one detail, a box the person can untick. A detail they haven't set, or unticked, is left out.

ScopeClaimsWhat you get
openidsubThe private identifier for your website
unidy_idunidy_idThe person's Unidy ID
profilenamegiven_namemiddle_namefamily_nameTheir names
emailemailemail_verifiedThe email address they chose for your website. Verified when it's the one they sign in to Unidy with
phonephone_numberphone_number_verifiedA verified phone number they chose for your website
addressaddressThe postal address they chose for your website: formatted, street_address, locality, region, postal_code, country
birthdatebirthdateDate of birth, YYYY-MM-DD
residenceresidenceCountry of residence, ISO 3166-1 alpha-2
languagelocaleTheir language
offline_accessA refresh token, so your website can renew the access token

Ask only for what you need. People untick what they see no reason for.

The browser SDK

For websites that run in the browser: Continue with Unidy without writing the flow yourself. One small file, no dependencies.

It comes as files with the developer guide and a sample website, not from npm. Copy the file to your site. To get the SDK, the guide and the sample website, write to partners@unidy.com.

dist/unidy.js
A plain <script>. Defines the global Unidy.
dist/unidy.esm.js
An ES module, for a bundler.
dist/unidy.d.ts
TypeScript types.
On your sign-in page
<div id="unidy"></div>
<script src="/unidy.js"></script>
<script>
  Unidy.init({ clientId: 'YOUR_CLIENT_ID', redirectUri: 'https://shop.example/callback.html', scopes: ['profile', 'email'] });
  Unidy.button('#unidy', { kind: 'continue' });
</script>
On your redirect page
Unidy.init({ clientId: 'YOUR_CLIENT_ID', redirectUri: 'https://shop.example/callback.html', scopes: ['profile', 'email'] });
Unidy.handleRedirect()
  .then(({ user }) => { /* find or create the account for user.sub */ location.assign('/account'); })
  .catch((error) => { if (error.code === 'access_denied') location.assign('/'); });

API

Unidy.init({ clientId, redirectUri, scopes, issuer, storage })
Sets the SDK up. Call it on every page first.
Unidy.button(element, { kind, theme, lang })
Draws the official Continue with Unidy button.
Unidy.signIn({ scopes, prompt })
Sends the person to Unidy with PKCE (S256), a state and a nonce.
Unidy.handleRedirect()
Checks the state, exchanges the code, validates the ID token and resolves to { user, kind, scopes }.
Unidy.user()
The signed-in person's claims, or null.
Unidy.details()
The details the person agreed to, from /api/v1/me.
Unidy.signOut()
Forgets the tokens and revokes the refresh token at Unidy.
Unidy.UnidyError
Every failure, with a code such as access_denied, not_linked or invalid_token.

Security checklist

  • Keep a client secret on your server. Never put one in a web page.
  • Use PKCE with S256 on every request. Browser websites can't skip it.
  • Send a random state and accept it once, only in the tab that asked.
  • Send a nonce and check it in the ID token.
  • Validate the ID token: signature from jwks_uri, iss, aud and exp.
  • Key accounts by sub. Never by email or unidy_id.
  • Register exact https redirect addresses, on one host.
  • Ask only for the scopes you need.
  • Pages that hold tokens: a strict Content-Security-Policy and no third-party scripts.
  • On sign out, revoke the refresh token at /oauth2/revoke. signOut() does it.

Available now

  • An OpenID Connect provider at id.unidy.com, with discovery
  • The authorization code flow with PKCE, for websites with a server or in the browser
  • A private sub per website
  • The scopes above, each one a detail the person approves
  • GET /api/v1/me with the access token
  • The browser SDK, the developer guide and a sample website

Register your website

A company with an approved Unidy business account registers its websites itself, in the Unidy app under Developers. No business account yet? Write to us and we'll help you open one.

Open as a page