Skip to content
Trustaige Docs

Set up an OpenID Connect app

Register an app for OpenID Connect sign-in. Discovery, the client ID, redirect URIs, client authentication, PKCE and signing keys.

  • Every plan
  • About 10 minutes
View as Markdown

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.

  1. Person to Your app: clicks Sign in
  2. Your app to Trustaige: redirect to authorize, with a code challenge
  3. Person and Trustaige: passkey
  4. Trustaige to Your app: redirect back with a code
  5. Your app to Trustaige: token request: code, verifier, client authentication
  6. Trustaige to Your app: ID token and access token

Create the app

You need the permission to manage apps in your organization.

  1. In the hub, go to Apps. Under In-house applications, click Create in-house app.
  2. Give it a name, and under Protocol choose OpenID Connect. Continue.
  3. Under What kind of application is this?, choose what it is (see the table below).
  4. Under Redirect URIs, add every address Trustaige may send people back to, for example <your-app-url>/callback.
  5. Check the summary and click Create application.
  6. 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:

KindForClient authenticationGrants
Web appA server that can keep a secretclient_secret_basic, with a client secretAuthorization code, refresh token
Single-page appCode that runs in the browsernone: a public client, PKCE requiredAuthorization code, refresh token
Native or mobileAn app installed on a devicenone: a public client, PKCE requiredAuthorization code, refresh token
CLI or deviceA tool with no browser of its ownnone: a public client, PKCE requiredAuthorization 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.

Issuerhttps://id.trustaige.com
Discoveryhttps://id.trustaige.com/.well-known/openid-configuration
Authorization endpointhttps://id.trustaige.com/oauth2/authorize
Token endpointhttps://id.trustaige.com/oauth2/token
UserInfo endpointhttps://id.trustaige.com/oauth2/userinfo
JWKShttps://id.trustaige.com/.well-known/jwks.json
End sessionhttps://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-configuration
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

Redirect 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. Plain http is accepted only for localhost, 127.0.0.1 and ::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:/callback aren’t accepted. A mobile app returns on an https link it owns, or a loopback address.
  • At the token endpoint, send the same redirect_uri as 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.

MethodHub labelWhat the app sends
client_secret_basicClient Secret BasicClient ID and secret in an HTTP Basic Authorization header
client_secret_postClient Secret Postclient_id and client_secret in the form body
private_key_jwtPrivate Key JWTA JWT signed with the app’s own private key. No secret exists
noneNone (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 https address 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:

ClaimRequirement
iss, subBoth the client ID
audhttps://id.trustaige.com/oauth2/token or https://id.trustaige.com
expRequired, and no more than an hour ahead
iatNot in the future
jtiRequired, 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.

  • S256 is the only method. Send code_challenge_method=S256 with 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_verifier with 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.