Connect an application
SAG is a standard OpenID Connect provider. If your framework already has an OpenID Connect client, you are configuring it, not writing an integration.
1. Point your library at discovery
https://auth.resoauth.cloud/.well-known/openid-configuration
That one URL is the whole configuration. It tells your library the authorisation endpoint, the token endpoint, where the signing keys live, and which algorithms and scopes this instance actually supports.
Read it rather than hard-coding what it says. The discovery document describes the running instance, not the software: a claim it does not list is a claim you will not get.
2. Get a client id
Publish a Client ID Metadata Document. Your
client_id is an https URL that serves your application's own metadata.
Nothing is registered with anybody, and this is the only route on the shared
hosted gateway today.
If your application needs to authenticate at the token endpoint, publish a
jwks_uri in the document and use private_key_jwt rather than a shared
secret. If none of that fits your application,
get in touch.
3. Use the authorisation code flow with PKCE
SAG accepts the authorisation code flow only, and PKCE is required. There is no implicit flow and no hybrid flow, because OAuth 2.1 removed both.
GET https://auth.resoauth.cloud/authorize
?response_type=code
&client_id=https://ledger.example.com/oauth-client
&redirect_uri=https://ledger.example.com/auth/callback
&scope=openid%20email
&state=<random, tied to the browser session>
&nonce=<random, checked in the id_token>
&code_challenge=<S256 of your verifier>
&code_challenge_method=S256
The person signs in, and SAG redirects back to your redirect_uri with
code and state. Exchange the code at /token:
curl -X POST https://auth.resoauth.cloud/token \
-d grant_type=authorization_code \
-d code=... \
-d redirect_uri=https://ledger.example.com/auth/callback \
-d client_id=https://ledger.example.com/oauth-client \
-d code_verifier=...
You get back an id_token and a short-lived access token. Verify the
id_token against /.well-known/jwks.json, and check iss, aud, exp,
and the nonce you sent. Any competent library does this for you. See
tokens and claims for what is inside.
Redirect URIs are matched exactly. No prefixes, no wildcards. OAuth 2.1 removed loose matching because every variant of it has been used to send an authorisation code to somebody else's page. The one exception is the loopback port for native applications, which RFC 8252 requires to be ignored.
4. Ask for the scopes you need
| Scope | Gets you |
|---|---|
openid | Required. Without it the request is refused |
email | email and email_verified |
profile | Name, picture, and locale, when an upstream provides them |
offline_access is accepted and does nothing: there are no refresh tokens.
Ask for the smallest set that works. profile is only advertised when the
instance can actually fill it, so read discovery rather than assuming.
The endpoints
Everything below is relative to https://auth.resoauth.cloud.
| Path | Method | What it is |
|---|---|---|
/.well-known/openid-configuration | GET | OpenID Connect discovery. Start here |
/.well-known/oauth-authorization-server | GET | The same instance described as an OAuth authorisation server (RFC 8414) |
/.well-known/oauth-protected-resource | GET | Describes /userinfo as a protected resource (RFC 9728) |
/.well-known/jwks.json | GET | The public keys that verify an id_token |
/jwks.json | GET | The same keys at the pre-RFC 8414 location, for older libraries |
/authorize | GET, POST | Where you send the person to sign in |
/callback | GET | Where an upstream provider returns them. Not for your application |
/token | POST | Exchange an authorisation code for an id_token |
/userinfo | GET, POST | Claims about the signed-in person, using the access token |
/logout | GET, POST | End the session (end_session_endpoint) |
/healthz | GET | Whether this instance can actually sign somebody in |
/alive | GET | Whether a process is listening at all |
The /authorize/* sub-paths (/authorize/email, /authorize/otp,
/authorize/resend, and the rest) belong to the sign-in screens themselves.
They are form targets, not an API, and their shape may change between
releases. Your application only ever needs /authorize.
/ is deliberately not a landing page. It returns 404 with "This is a
sign-in service. Start from the application you want to use."
Before you go live
- Verify the
id_tokensignature. Against the published JWKS, every time. Do not trust a token because it arrived over TLS. - Check
nonceandstate. Your library probably does. Confirm it. - Decide what
submeans to you. It is derived from the verified email address, so a person who changes address becomes a differentsub. This is a deliberate choice, explained in ADR 0011. - Decide whether an email code is good enough. If parts of your application need more, demand it explicitly rather than hoping. See asking for a stronger sign-in.
- Handle sign-out.
/logoutmay ask the person to confirm, because a session can be shared with other applications. See the sign-in experience.
A worked example
SAG ships a small relying party that serves its own metadata document and signs in with it. It is the shortest complete example there is:
git clone https://github.com/RESOAuth/smart-access-gateway
cd smart-access-gateway
EXAMPLE_USE_CIMD_AND_PUBLIC_CLIENT=1 npm run example
The source is
examples/relying-party/server.js.
By default it points at a local SAG; set its issuer to
https://auth.resoauth.cloud to try the hosted one.