# 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.

Source: https://docs.trustaige.com/sign-in/oidc

Replace these placeholders with your organization's own values:

- `<your-client-id>`: App client ID, for example `q3Jx8vN2mZ0bT5sLwK7yHc4dR1fGaE9uPiO6nVjXkMs`. On the app’s page in the hub, under Credentials.
- `<your-app-url>`: Your app’s address, for example `https://app.example.com`. Scheme and host, no path: where your app runs.
- `<your-client-secret>`: Client secret, a secret. From the hub: on the app’s page under Credentials. Keep it on your server.

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](/sign-in).

1. Person → Your app: clicks Sign in
2. Your app → Trustaige: redirect to authorize, with a code challenge
3. Person ↔ Trustaige: passkey
4. Trustaige → Your app: redirect back with a code
5. Your app → Trustaige: token request: code, verifier, client authentication
6. Trustaige → 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:

| 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` |

```bash
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.

```http title="Authorization request" {4,7-8}
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
```

> **Note:** An app for your customers, rather than your organization's people, signs in
> through your organization's own issuer instead. See
> [Sign people in and out](/customer-credentials/sign-in).

## 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**.

| 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.

```bash title="Exchange the code (client_secret_basic)" {2,6}
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`:

| 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 |

## 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**.

```js title="pkce.js"
import crypto from 'node:crypto';

const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
```

```go title="pkce.go"
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[:])
}
```

```python title="pkce.py"
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](/tools/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`.
