Connect a client
Register an OAuth client and use authorization code with PKCE or client credentials.
Use authorization code with S256 PKCE when a person signs in. Use client credentials when a backend or scheduled job acts as itself. The Better Auth OAuth provider guide covers the underlying protocol and registration fields.
Register the application
Ask an identity administrator to open Applications and create your application. Supply its name, callback URLs, required scopes, and grant types.
| Client | Grants | Token endpoint authentication |
|---|---|---|
| Server application with user sign-in | authorization_code; add refresh_token if needed | The registered confidential method, such as client_secret_basic or client_secret_post |
| Browser-only or native application | authorization_code; add refresh_token if approved | none, with S256 PKCE |
| Backend service or scheduled job | client_credentials | A confidential method |
The portal's application type, web or native, controls redirect URI rules. It does not determine whether the client can keep a secret. Confirm the stored token_endpoint_auth_method. In the current portal implementation, the public-client checkbox records metadata but the create request does not set token_endpoint_auth_method: "none". Have the administrator verify the OAuth registration before connecting a browser-only or native client. Never ship a secret to work around a registration mismatch.
Register each callback exactly. A different scheme, port, path, or trailing slash can cause a redirect mismatch. Register post-logout redirect URLs separately if your application uses OIDC logout.
Copy the client secret when the server returns it. Secrets are stored hashed. Rotation invalidates the previous secret immediately, so coordinate the application's secret update with the administrator.
Configure discovery
Give your OIDC library these values, using its equivalent configuration names:
issuer = https://dev.auth.graphxserver.io
discovery_url = https://dev.auth.graphxserver.io/.well-known/openid-configuration
client_id = <registered client ID>
client_authentication = <registered token endpoint authentication method>
redirect_uri = https://app.example.com/oauth/callback
response_type = code
pkce_method = S256
scope = openid profile email org.roles orders.read
resource = https://dev.api.graphxserver.io/ordersorders.read and the Orders resource illustrate one API integration. Ask the API owner which scopes your endpoint requires. Do not request every available scope.
For the shell examples, set the public configuration below and load CLIENT_ID and any CLIENT_SECRET from your environment or secret manager. Set response values such as AUTHORIZATION_CODE and ACCESS_TOKEN from your application's flow, without committing or logging them.
IDENTITY_ORIGIN='https://dev.auth.graphxserver.io'
RESOURCE='https://dev.api.graphxserver.io/orders'
REDIRECT_URI='https://app.example.com/oauth/callback'Sign a user in
1. Create a sign-in transaction
Generate a random state, an OIDC nonce, and a PKCE verifier for each attempt. Store them in a short-lived transaction bound to the browser. Derive code_challenge as the base64url encoding of the verifier's SHA-256 digest, without padding. An OAuth/OIDC library should handle this for you.
Keep the verifier out of the authorization URL. Bind the intended callback, resource, and scopes to the same transaction.
2. Redirect the browser to authorization
Construct this URL with a URL encoder. The example breaks it across lines so the parameters are readable.
https://dev.auth.graphxserver.io/api/auth/oauth2/authorize
?response_type=code
&client_id=<CLIENT_ID>
&redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback
&scope=openid%20profile%20email%20org.roles%20orders.read
&resource=https%3A%2F%2Fdev.api.graphxserver.io%2Forders
&state=<RANDOM_STATE>
&nonce=<RANDOM_NONCE>
&code_challenge=<S256_CHALLENGE>
&code_challenge_method=S256The identity server handles sign-in, account selection, organization selection, and consent as needed. Keep those steps on the identity server. Request org.roles, org.name, or org.list when you need organization context; these scopes require an active organization in this deployment.
3. Validate the callback
Handle an OAuth error response before attempting an exchange. Verify that the returned state matches the transaction and reject missing, expired, or reused transactions. Validate the authorization response's iss against your configured issuer when present. Exchange the code once with the original redirect URI and verifier.
curl --fail-with-body \
--request POST "$IDENTITY_ORIGIN/api/auth/oauth2/token" \
--user "$CLIENT_ID:$CLIENT_SECRET" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode "code=$AUTHORIZATION_CODE" \
--data-urlencode "redirect_uri=$REDIRECT_URI" \
--data-urlencode "code_verifier=$CODE_VERIFIER" \
--data-urlencode "resource=$RESOURCE"This example uses client_secret_basic. For a client registered with client_secret_post, omit --user and send client_id and client_secret as form fields. For a public client registered with none, omit --user, send client_id, and send no secret. PKCE still applies.
The successful response contains access_token, token_type, and expires_in. It can also contain scope, an id_token when using OpenID Connect, and a refresh_token when offline access is granted. Do not log the response body.
Let your OIDC library verify the ID token's signature, issuer, client audience, expiry, and nonce before creating your application's session. Send the access token to the API.
4. Call the resource
curl --fail-with-body "$RESOURCE/<documented-api-path>" \
--header "Authorization: Bearer $ACCESS_TOKEN"Replace <documented-api-path> with an endpoint documented by that API. The resource identifier names an audience; it does not describe the API's routes.
Read the user's identity
Call the discovered userinfo_endpoint with a user access token that has the required OIDC scopes and an audience accepted by that endpoint. An Orders-only token is intended for Orders. Request the identity resource when your integration needs UserInfo access.
curl --fail-with-body "$IDENTITY_ORIGIN/api/auth/oauth2/userinfo" \
--header "Authorization: Bearer $IDENTITY_ACCESS_TOKEN"Check that UserInfo's sub matches the validated ID token's sub. Use the stable subject ID for identity, rather than email, which can change. See organization claims for the Graphxsource additions.
Renew access
Enable refresh_token on the client and include offline_access in the original authorization request. Keep the refresh token in server-side storage where possible.
curl --fail-with-body \
--request POST "$IDENTITY_ORIGIN/api/auth/oauth2/token" \
--user "$CLIENT_ID:$CLIENT_SECRET" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode "refresh_token=$REFRESH_TOKEN" \
--data-urlencode "resource=$RESOURCE"Use the client's registered authentication method here too. Store a returned replacement refresh token atomically with the new access token. Serialize concurrent renewals for one session. If renewal fails with invalid_grant, clear the application's session and start sign-in again. A refresh request cannot expand the original grant's resources or scopes.
Call an API without a user
Have the administrator enable client_credentials and assign a nonempty machine scope allowance, client_credentials_scopes. In the current portal, creating a client with this grant copies the selected non-OIDC scopes into that allowance. openid, profile, email, and offline_access are excluded. A client registered through another path needs the allowance set by an administrator.
IDENTITY_ORIGIN='https://dev.auth.graphxserver.io'
RESOURCE='https://dev.api.graphxserver.io/orders'
curl --fail-with-body \
--request POST "$IDENTITY_ORIGIN/api/auth/oauth2/token" \
--user "$CLIENT_ID:$CLIENT_SECRET" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=orders.read' \
--data-urlencode "resource=$RESOURCE"Set CLIENT_ID and CLIENT_SECRET through your local environment or secret manager. This request uses client_secret_basic; adapt it if the registration requires a different method.
A client credentials token represents the client. Do not assume it has a user, organization membership, or a user session. Obtain a new token before expiry rather than relying on a refresh token. The user permission-check endpoints reject this subject type.
End access
Delete your application's session on logout. When needed, revoke its refresh token through the discovered revocation_endpoint using the client's registered authentication method.
curl --fail-with-body \
--request POST "$IDENTITY_ORIGIN/api/auth/oauth2/revoke" \
--user "$CLIENT_ID:$CLIENT_SECRET" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "token=$REFRESH_TOKEN" \
--data-urlencode 'token_type_hint=refresh_token'For identity-server logout, use the discovered end_session_endpoint with your OIDC library and an approved post-logout redirect. Confirm that the client has end-session enabled. Clearing your local session alone does not sign the user out of the identity server. APIs that only verify JWTs locally can continue accepting an issued token until it expires; see revocation checks.