Yoursafe · Integration
Yoursafe

Yoursafe ID Integration

Add “Sign in with Yoursafe ID” with the OpenID Connect authorization code flow. Your backend talks to Yoursafe; the browser only follows redirects.

Before you start

You need a client_id, a client_secret, and a registered redirect URI. Getting Started shows how to create them. Every value on this page is an example: replace hosts, credentials, and tokens with your own.

Systems involved

(Web) App

The user's browser or client app. Here: http://localhost:5173.

Your backend

Your server, the OAuth client. It holds the secret and the tokens. Here: http://localhost:3000.

Yoursafe

The OpenID provider at accounts.yoursafe.com.

Arrows show the direction of each HTTP request. Redirects are marked as such: your backend answers and the browser makes the next request.

The flow at a glance

  1. The app sends the user to /auth/login on your backend.
  2. Your backend redirects the browser to Yoursafe with state and nonce.
  3. The user signs in at Yoursafe and is redirected to your callback with a code.
  4. Your backend checks state, exchanges the code for tokens, and validates the ID token.
  5. Your backend starts its own session and redirects the user back to the app.
  6. The app reads the signed-in user from your backend, and can sign out there.

Step 1Start sign-in

Your backend loads the discovery document, creates state and nonce, and redirects the browser to Yoursafe.

Login route

User action

User clicks “Sign in with Yoursafe ID”.

Request

(Web) App→Your backend

GET http://localhost:3000/auth/login

Open this route with a full-page navigation (a link or window.location), not fetch, so the browser follows the redirects that come next.

Discovery

Your backend reads the endpoint URLs from the discovery document. Fetch it once and cache it, rather than on every sign-in.

Request

Your backend→Yoursafe

GET https://accounts.yoursafe.com/.well-known/openid-configuration
Response

Yoursafe→Your backend

Abbreviated. The live document is the source of truth; ... stands for keys such as introspection and revocation.

{ "issuer": "https://accounts.yoursafe.com", "authorization_endpoint": "https://accounts.yoursafe.com/oauth2/authorize", "token_endpoint": "https://accounts.yoursafe.com/oauth2/token", "userinfo_endpoint": "https://accounts.yoursafe.com/userinfo", "end_session_endpoint": "https://accounts.yoursafe.com/connect/logout", "jwks_uri": "https://accounts.yoursafe.com/oauth2/jwks", "response_types_supported": ["code"], "id_token_signing_alg_values_supported": ["RS256"], "scopes_supported": ["openid", "default", "profile", "platform"], ... }

State and nonce

Your backend(no request)

Generate two fresh random values for every sign-in, with a cryptographically secure generator, and store both in the user's server-side session.

state=FfMX8LwzcisrQWH190RkCg nonce=2RSzKHpxWleZuA3JOIcrgQ
state
Yoursafe returns it on the callback. You compare it with the stored value, which proves the callback belongs to a sign-in your backend started (CSRF protection).
nonce
Yoursafe puts it in the ID token. You compare it with the stored value, which ties the token to this sign-in and stops a token from another sign-in being replayed.

Authorize redirect

Your backend answers GET /auth/login with a redirect to the authorization_endpoint. All six parameters are required.

Response

Your backend→(Web) App(redirect)

HTTP/1.1 302 Found Location: https://accounts.yoursafe.com/oauth2/authorize ?response_type=code &client_id=cljxc37g6f27s &redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fcallback &scope=openid%20default &state=FfMX8LwzcisrQWH190RkCg &nonce=2RSzKHpxWleZuA3JOIcrgQ

Line breaks are for reading only. The real Location is one URL.

response_type
Always code.
client_id
Your Yoursafe ID client.
redirect_uri
Your callback, exactly as registered, URL-encoded. Send the same value again in the token exchange.
scope
Space-separated (%20). Always include openid, plus the Yoursafe scopes your client is registered for: default, platform, or profile. Scopes decide which claims you get; see the Claims Glossary.
state
The value you just stored.
nonce
The value you just stored.

Step 2Handle the callback

Yoursafe sends the browser back to your redirect URI. Your backend checks state, exchanges the code for tokens, validates the ID token, and starts its own session.

Callback

User action

User signs in at Yoursafe and approves the requested scopes.

Request

(Web) App→Your backend(redirect from Yoursafe)

The query string carries either a code or an OAuth error, always with state.

Success: an authorization code.

GET http://localhost:3000/auth/callback?code=xDE-FXbh7l1KWE...&state=FfMX8LwzcisrQWH190RkCg

Error: no code, so there is nothing to exchange.

GET http://localhost:3000/auth/callback?error=access_denied&error_description=...&state=FfMX8LwzcisrQWH190RkCg

Check state, then errors

Your backend(no request)

  • Compare state with the value in the session. If it is missing or different, stop and respond with an error. Do not exchange the code.
  • Remove state from the session, so it works only once.
  • If the query has an error, show the user a message and a way to try again. access_denied means the user did not approve; invalid_scope means you asked for a scope your client is not registered for.

Token exchange

Send the code to the token_endpoint together with the same redirect_uri you used in Step 1.

Request

Your backend→Yoursafe

PUT https://accounts.yoursafe.com/oauth2/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=xDE-FXbh7l1KWE... &redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fcallback &client_id=cljxc37g6f27s &client_secret=***

The body is one URL-encoded string, broken here for reading. Yoursafe uses PUT here, not the POST most OAuth libraries send. If your library cannot change the method, make this one request yourself. The code works once and expires quickly: exchange it straight away, and start a new sign-in if the exchange fails.

Response

Yoursafe→Your backend

{ "access_token": "eyJ...abc", "id_token": "eyJ...xyz", "token_type": "Bearer", "expires_in": 1800, "scope": "openid default" }

Depending on your client, the response can also include a refresh_token. Keep all tokens on the server.

Validate the ID token

The id_token is a signed JWT. Validate it before you trust any claim in it. A maintained OpenID Connect or JOSE library does these checks for you.

Signature
RS256, with the key from jwks_uri whose kid matches the token header. Cache the key set, and fetch it again when a token has an unknown kid.
iss
Exactly https://accounts.yoursafe.com.
aud
Contains your client_id.
exp
In the future, allowing a little clock skew.
nonce
Equal to the value in the session. Then remove it from the session.

A decoded ID token payload for openid default:

{ "iss": "https://accounts.yoursafe.com", "sub": "G0Y999.yoursafe.id", "aud": "cljxc37g6f27s", "exp": 1793697600, "iat": 1793695800, "nonce": "2RSzKHpxWleZuA3JOIcrgQ", "aliastoken": "G0Y999.yoursafe.id", "accountstatus": "active", "countrycode": "NL" }

Start your session and redirect

Store the tokens and claims in your server-side session. The same callback request then ends with a redirect to the app, carrying only a session cookie.

Response

Your backend→(Web) App(redirect)

HTTP/1.1 302 Found Location: http://localhost:5173/login/success Set-Cookie: sid=8f3c...; Path=/; HttpOnly; SameSite=Lax

HttpOnly keeps the cookie away from JavaScript. Add Secure once you serve over HTTPS. From here on, your own session authorizes the app's requests; the browser never calls Yoursafe.

User action

User lands on the app, signed in.

Step 3Read the user's claims

The app asks your backend who is signed in. Your backend answers from the validated ID token and, when it needs more, from the UserInfo endpoint.

Your profile route

Request

(Web) App→Your backend

GET http://localhost:3000/api/me Cookie: sid=8f3c...

From the app, send the cookie with fetch(url, { credentials: "include" }).

Response

Your backend→(Web) App

The shape is up to you. Return only what the app needs, and 401 when there is no session.

{ "authenticated": true, "user": { "sub": "G0Y999.yoursafe.id", "aliastoken": "G0Y999.yoursafe.id", "accountstatus": "active", "countrycode": "NL" } }

UserInfo (optional)

To fetch the claims fresh, call the userinfo_endpoint with the access token. Which claims come back depends on the granted scopes; the Claims Glossary lists them.

Request

Your backend→Yoursafe

GET https://accounts.yoursafe.com/userinfo Authorization: Bearer eyJ...abc
Response

Yoursafe→Your backend

{ "sub": "G0Y999.yoursafe.id", ... }

The access token is valid for expires_in seconds from the token response.

Step 4Sign out

End your own session. To sign the user out at Yoursafe as well, send the browser on to the end_session_endpoint.

Logout route

Request

(Web) App→Your backend

POST http://localhost:3000/auth/logout Cookie: sid=8f3c...

Submit this as a form, a full-page navigation, so the browser can follow the redirect in the response.

Response

Your backend→(Web) App(redirect)

Delete the session, clear the cookie, and redirect to Yoursafe with the ID token you stored.

HTTP/1.1 303 See Other Location: https://accounts.yoursafe.com/connect/logout ?id_token_hint=eyJ...xyz &post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A5173%2F &client_id=cljxc37g6f27s Set-Cookie: sid=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax

post_logout_redirect_uri must be registered for your client. To sign out of your app only, redirect to the app instead; the user then stays signed in at Yoursafe.

Implementation checklist

  • Register the redirect URI exactly, and send the identical value to authorize and to the token exchange.
  • Create a fresh state and nonce for every sign-in, check both, and use each once.
  • Validate the ID token's signature, iss, aud, exp, and nonce before you use a claim.
  • Keep client_secret and all tokens on the server. The browser only gets an HttpOnly session cookie.
  • Call the token endpoint with PUT.
  • Cache the discovery document and the key set. Set timeouts on every call to Yoursafe.
  • Serve every redirect URI over HTTPS in production.

Every endpoint, parameter, and response is in the Yoursafe ID API reference.

Next

Claims Glossary

Three scopes, each on its own page. Start with Default, then open Platform or Profile when the integration needs those fields.