Your values Fill in your own to see them in every example
On the app’s page in the hub, under Credentials.
That doesn’t look right yet. For example: q3Jx8vN2mZ0bT5sLwK7yHc4dR1fGaE9uPiO6nVjXkMs
Scheme and host, no path: where your app runs.
That doesn’t look right yet. For example: https://app.example.com
Client secret stays a marked placeholder here, never filled in: the hub gives you secrets, and only your server should hold them.
Saved in this browser only, for every page of these docs. Nothing you type leaves this page.
Trustaige is an OpenID Connect provider. Register your app in the hub, point any OpenID Connect library at the issuer, and run the authorization code flow: the person signs in with their passkey and your app receives an ID token that says who they are.
This page covers what you set up once: the app in the hub, discovery, redirect URIs, how your app proves who it is, PKCE, and the keys that sign its tokens. For what the tokens carry, see Sign in with Trustaige.
- Person to Your app: clicks Sign in
- Your app to Trustaige: redirect to authorize, with a code challenge
- Person and Trustaige: passkey
- Trustaige to Your app: redirect back with a code
- Your app to Trustaige: token request: code, verifier, client authentication
- Trustaige to Your app: ID token and access token
Create the app
You need the permission to manage apps in your organization.
- In the hub, go to Apps. Under In-house applications, click Create in-house app.
- Give it a name, and under Protocol choose OpenID Connect. Continue.
- Under What kind of application is this?, choose what it is (see the table below).
- Under Redirect URIs, add every address Trustaige may send people back
to, for example
<your-app-url>/callback. - Check the summary and click Create application.
- On the app’s page, Credentials shows the Client ID, the Auth Method, and for a web app the Client Secret.
The kind decides how the app proves who it is and which grants it holds:
| Kind | For | Client authentication | Grants |
|---|---|---|---|
| Web app | A server that can keep a secret | client_secret_basic, with a client secret | Authorization code, refresh token |
| Single-page app | Code that runs in the browser | none: a public client, PKCE required | Authorization code, refresh token |
| Native or mobile | An app installed on a device | none: a public client, PKCE required | Authorization code, refresh token |
| CLI or device | A tool with no browser of its own | none: a public client, PKCE required | Authorization code, refresh token, device code |
Choose who can use it. In the app’s Access section, assign the groups whose people may sign in. With none assigned, the section reads Open to everyone.
Point your library at the issuer
The issuer is the same for every organization. Your client ID tells Trustaige which organization the app belongs to.
| Issuer | https://id.trustaige.com |
| Discovery | https://id.trustaige.com/.well-known/openid-configuration |
| Authorization endpoint | https://id.trustaige.com/oauth2/authorize |
| Token endpoint | https://id.trustaige.com/oauth2/token |
| UserInfo endpoint | https://id.trustaige.com/oauth2/userinfo |
| JWKS | https://id.trustaige.com/.well-known/jwks.json |
| End session | https://id.trustaige.com/oauth2/logout |
curl https://id.trustaige.com/.well-known/openid-configuration
Trustaige supports the code response type, returned in the query string
(response_mode=query). Every authorization request needs the openid scope.
GET https://id.trustaige.com/oauth2/authorize
?response_type=code
&client_id=<your-client-id>
&redirect_uri=<your-app-url>/callback
&scope=openid%20email%20profile
&state=<random_state>
&code_challenge=<code_challenge>
&code_challenge_method=S256
curl https://id.trustaige.com/.well-known/openid-configurationGET https://id.trustaige.com/oauth2/authorize
?response_type=code
&client_id=<your-client-id>
&redirect_uri=<your-app-url>/callback
&scope=openid%20email%20profile
&state=<random_state>
&code_challenge=<code_challenge>
&code_challenge_method=S256Redirect URIs
Trustaige sends the person back only to an address on the app’s list, and compares the whole address, character for character: scheme, host, port, path and query.
- Use
https. Plainhttpis accepted only forlocalhost,127.0.0.1and::1. - For those loopback addresses, any port matches, so a native app can listen on whichever port the system gives it. The scheme, host and path still have to match.
- Custom URL schemes such as
com.example.app:/callbackaren’t accepted. A mobile app returns on anhttpslink it owns, or a loopback address. - At the token endpoint, send the same
redirect_urias in the authorization request.
Change the list under Edit app → OIDC Configuration → Redirect URIs. The app’s page lists them under Endpoints and grants.
Client authentication
How the app proves who it is at the token endpoint. Change it under Edit app → OIDC Configuration → Token Endpoint Auth Method.
| Method | Hub label | What the app sends |
|---|---|---|
client_secret_basic | Client Secret Basic | Client ID and secret in an HTTP Basic Authorization header |
client_secret_post | Client Secret Post | client_id and client_secret in the form body |
private_key_jwt | Private Key JWT | A JWT signed with the app’s own private key. No secret exists |
none | None (Public Client) | client_id only. PKCE carries the proof |
An app with a secret can send it either way, Basic or in the body; Trustaige accepts both whichever was chosen. If it sends both, both must be right.
curl https://id.trustaige.com/oauth2/token \
-u '<your-client-id>:<your-client-secret>' \
-d grant_type=authorization_code \
-d code=<code_from_callback> \
-d redirect_uri=<your-app-url>/callback \
-d code_verifier=<code_verifier>
A code works once, and for 10 minutes.
Rotate the secret with the regenerate button beside Client Secret on the app’s page. The current secret stops working at once, so have the new one ready to deploy.
private_key_jwt
No shared secret exists: the app signs a short JWT with its private key, and Trustaige checks it against the app’s public keys. Under Token Endpoint Auth Method choose Private Key JWT, then give the public keys one of two ways:
- Hosted JWKS URL: an
httpsaddress that serves the key set. Trustaige caches it, and fetches it again when an assertion names a key it hasn’t seen, so rotating keys needs nothing in the hub. - Pasted key set: a JWKS document stored with the app. Rotating means pasting a new set.
Verify keys checks each key before you save. Assertion Signing Algorithm accepts Any supported (RS256, ES256, PS256 or EdDSA) or pins one.
The assertion goes in the token request as client_assertion, with
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer:
| Claim | Requirement |
|---|---|
iss, sub | Both the client ID |
aud | https://id.trustaige.com/oauth2/token or https://id.trustaige.com |
exp | Required, and no more than an hour ahead |
iat | Not in the future |
jti | Required, and never reused: a second use is refused |
kid (header) | Required when the key set holds more than one signing key |
curl https://id.trustaige.com/oauth2/token \
-u '<your-client-id>:<your-client-secret>' \
-d grant_type=authorization_code \
-d code=<code_from_callback> \
-d redirect_uri=<your-app-url>/callback \
-d code_verifier=<code_verifier>PKCE
PKCE ties the code to the app that asked for it, so an intercepted code is useless.
S256is the only method. Sendcode_challenge_method=S256with every challenge: a request that leaves the method out is refused.- The challenge is the unpadded base64url SHA-256 of a random verifier of 43
to 128 characters. Send the verifier as
code_verifierwith the code. - A public client must use PKCE. A web app may; Trustaige checks the verifier whenever a challenge was sent. To require it from a web app, turn on Require PKCE under Edit app.
import crypto from 'node:crypto';
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
)
func pkcePair() (verifier, challenge string) {
b := make([]byte, 32)
rand.Read(b)
verifier = base64.RawURLEncoding.EncodeToString(b)
sum := sha256.Sum256([]byte(verifier))
return verifier, base64.RawURLEncoding.EncodeToString(sum[:])
}import base64, hashlib, secrets
verifier = secrets.token_urlsafe(32)
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode() import crypto from 'node:crypto';
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
)
func pkcePair() (verifier, challenge string) {
b := make([]byte, 32)
rand.Read(b)
verifier = base64.RawURLEncoding.EncodeToString(b)
sum := sha256.Sum256([]byte(verifier))
return verifier, base64.RawURLEncoding.EncodeToString(sum[:])
}import base64, hashlib, secrets
verifier = secrets.token_urlsafe(32)
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode() Signing keys
ID tokens are signed with EdDSA unless the app asks for another algorithm under Edit app → ID Token Signing Algorithm: RS256, ES256 or PS256. Use EdDSA unless your library can’t verify it.
Verify tokens with the keys at https://id.trustaige.com/.well-known/jwks.json,
choosing the key by the token’s kid. The set holds a key for each algorithm.
Trustaige replaces its signing keys about once a week, and a new key signs as
soon as it’s published, so when a token names a kid you haven’t cached, fetch
the set again. A retired key stays in the set for two more days. ID tokens and
access tokens last an hour.
Check it works
Sign in through your app. Paste the ID token into the
token decoder: iss is https://id.trustaige.com,
aud is your client ID, and sub is the person’s ID in your organization.
Each sign-in appears in the hub’s Audit log.
Turn it off or remove it
- Pause sign-in: switch the app to Disabled at the top of its page.
Sign-in then stops with
Application is disabled. - Cut off one copy of the app: regenerate the client secret, or replace its keys.
- Remove it: in the app’s menu, choose Delete app.
Troubleshooting
“invalid redirect uri”
The redirect_uri isn’t on the app’s list exactly as sent. Compare them
character for character, including a trailing slash, under Edit app →
Redirect URIs.
“This application must use PKCE (code_challenge) to sign in.”
The app is a public client, or has Require PKCE on, and the request had no
code_challenge. Turn on PKCE in your library.
“unsupported code_challenge_method”
The request sent a challenge without code_challenge_method=S256, or with
plain. Send S256.
“Unauthorized request” from the token endpoint
Client authentication failed: a wrong or rotated secret, a client ID from
another app, or a private_key_jwt assertion that broke a rule in the table
above. Check the Auth Method on the app’s page matches what your library
sends.
“redirect_uri mismatch” or “code_verifier mismatch” from the token endpoint
The token request must carry the same redirect_uri as the authorization
request, and the verifier that produced its challenge.
“code already used”
A code works once. Start the sign-in again; don’t retry the token request with the same code.
“Access denied: you are not assigned to this application”
The app has groups assigned under Access, and the person isn’t in any of them. Add them to one of those groups, or assign a group they’re in.
Your library can't find a key to verify the ID token
Trustaige has rotated its keys since your library cached them. Fetch
https://id.trustaige.com/.well-known/jwks.json again when a token names an
unknown kid.