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.
- 1Your app
Send the person to Unidy
With a PKCE challenge, a state and a nonce.
GET /oauth2/auth - 2id.unidy.com
Unidy shows your request
Your website's name, logo and web address, and each detail you ask for.
- 3The person
Signs in and decides
Unticks what they don't want to share. Then Allow, Allow once or Deny.
- 4Your app
A code comes back
To your registered redirect address, with your state.
?code=…&state=… - 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 - 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
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
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
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
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
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
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
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
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.
| Scope | Claims | What you get |
|---|---|---|
openid | sub | The private identifier for your website |
unidy_id | unidy_id | The person's Unidy ID |
profile | namegiven_namemiddle_namefamily_name | Their names |
email | emailemail_verified | The email address they chose for your website. Verified when it's the one they sign in to Unidy with |
phone | phone_numberphone_number_verified | A verified phone number they chose for your website |
address | address | The postal address they chose for your website: formatted, street_address, locality, region, postal_code, country |
birthdate | birthdate | Date of birth, YYYY-MM-DD |
residence | residence | Country of residence, ISO 3166-1 alpha-2 |
language | locale | Their language |
offline_access | A 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.
<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>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.