OAuth 2.1 / OIDC Integration
Vyntech Account is a full OAuth 2.1 and OpenID Connect provider. Use the authorization code flow with PKCE for web apps, SPAs, and mobile apps. This guide covers the full flow, endpoints, scopes, and token handling.
Base URL: https://id.vyntech.com.au — All OAuth/OIDC endpoints are served from this origin.
Overview
OAuth 2.1 is the latest evolution of the OAuth framework. It consolidates security best practices from OAuth 2.0 extensions into a single specification, removing legacy flows that are no longer considered secure.
| Change | OAuth 2.0 | OAuth 2.1 (Vyntech) |
|---|---|---|
| PKCE | Optional (public clients only) | Required for all clients |
| Implicit flow | Allowed | Removed |
| ROPC grant | Allowed | Removed |
| Refresh token rotation | Recommended | Enforced |
Supported grant types: authorization_code and refresh_token.
OIDC Discovery
The OpenID Connect Discovery endpoint publishes all configuration your client library needs to auto-configure itself — issuer, endpoints, supported scopes, signing algorithms, and more. Most OIDC client libraries accept a single discovery URL and handle the rest.
/.well-known/openid-configurationPublicOpenID Connect Discovery document. Returns all supported endpoints, scopes, and algorithms.
Authorization Code Flow with PKCE
This is the only supported flow. It works for all client types — web apps, SPAs, mobile apps, and CLI tools. PKCE (Proof Key for Code Exchange) protects against authorization code interception attacks.
Generate code_verifier and code_challenge
Create a cryptographically random code_verifier (43–128 characters, URL-safe). Derive the code_challenge as the Base64-URL-encoded SHA-256 hash of the verifier.
// JavaScript
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const codeVerifier = btoa(String.fromCharCode(...array))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const digest = await crypto.subtle.digest("SHA-256",
new TextEncoder().encode(codeVerifier));
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");Redirect to authorization endpoint
Redirect the user's browser to the authorization endpoint with the required parameters.
GET https://id.vyntech.com.au/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.com/callback &scope=openid profile email offline_access roles &state=RANDOM_STATE_VALUE &code_challenge=CODE_CHALLENGE &code_challenge_method=S256
response_typeAlways "code"client_idYour registered OAuth client IDredirect_uriMust match a registered redirect URIscopeSpace-separated list of requested scopesstateRandom value to prevent CSRF (verified on callback)code_challengeBase64-URL-encoded SHA-256 of code_verifiercode_challenge_methodAlways "S256"User authenticates
Vyntech Account presents the login page. The user enters their credentials and completes MFA if required by the tenant's security policy. This step is handled entirely by Vyntech Account — your app does not see the user's password.
Receive authorization code
After successful authentication, the user is redirected back to your redirect_uri with an authorization code and the state parameter.
GET https://yourapp.com/callback ?code=AUTH_CODE_HERE &state=RANDOM_STATE_VALUE
Always verify that the state matches what you sent in Step 2. Authorization codes expire in 60 seconds and can only be used once.
Exchange code for tokens
POST the authorization code along with the original code_verifier to the token endpoint. The server verifies that SHA-256(code_verifier) matches the code_challenge sent in Step 2.
/oauth/tokenPublicExchange authorization code for access, ID, and refresh tokens.
Validate ID token and extract user info
The id_token is a JWT signed with EdDSA. Verify the signature using the public key from JWKS, then extract user claims. Alternatively, call the userinfo endpoint with the access token.
// JavaScript — verify with jose library
import { createRemoteJWKSet, jwtVerify } from "jose";
const JWKS = createRemoteJWKSet(
new URL("https://id.vyntech.com.au/.well-known/jwks.json")
);
const { payload } = await jwtVerify(tokens.id_token, JWKS, {
issuer: "https://id.vyntech.com.au",
audience: "YOUR_CLIENT_ID",
});
console.log(payload.sub); // user ID
console.log(payload.email); // user email
console.log(payload.roles); // ["admin", "member"]Scopes
Scopes determine what information and capabilities the tokens will include. Request them as a space-separated list in the scope parameter.
| Scope | Claims / Effect | Required |
|---|---|---|
| openid | Enables OIDC — returns id_token with sub, iss, aud, exp, iat | Yes |
| profile | Adds display_name to id_token and userinfo | No |
Adds email and email_verified to id_token and userinfo | No | |
| offline_access | Issues a refresh_token alongside the access token | No |
| roles | Adds roles array to id_token and access token | No |
Token Exchange
Exchange an authorization code for tokens. This is the same endpoint shown in Step 5 above, documented here with full request/response details for reference.
/oauth/tokenPublicExchange authorization code for tokens (grant_type=authorization_code).
{
"grant_type": "authorization_code",
"code": "AUTH_CODE_HERE",
"redirect_uri": "https://yourapp.com/callback",
"client_id": "YOUR_CLIENT_ID",
"code_verifier": "YOUR_CODE_VERIFIER"
}Userinfo Endpoint
Fetch the authenticated user's profile information using the access token. The claims returned depend on the scopes granted during authorization.
/oauth/userinfo🔒 AuthGet the current user's profile claims using a valid access token.
Refresh Token
Use the refresh token to obtain a new access token without requiring the user to re-authenticate. Refresh tokens are rotated on every use — the old token is immediately invalidated and a new one is returned.
/oauth/tokenPublicRefresh an access token using a valid refresh token (grant_type=refresh_token).
Client Registration
Register an OAuth client to receive a client_id. Each client has a name, one or more redirect URIs, and a list of allowed scopes. Clients are scoped to a tenant.
/oauth/clients🔒 AuthRegister a new OAuth client for your tenant.
{
"name": "My Web App",
"redirect_uris": [
"https://myapp.com/callback",
"http://localhost:3000/callback"
],
"scopes": ["openid", "profile", "email", "offline_access", "roles"]
}ID Token Claims
The id_token is a JWT (JSON Web Token) signed with EdDSA (Ed25519). It contains claims about the authenticated user. The exact claims depend on the scopes requested.
| Claim | Type | Description |
|---|---|---|
| sub | string | User ID (unique identifier) |
| iss | string | Issuer — always https://id.vyntech.com.au |
| aud | string | Audience — your client_id |
| exp | number | Expiration time (Unix timestamp) |
| iat | number | Issued-at time (Unix timestamp) |
| nonce | string | Echoed from authorization request (if provided) |
| string | User email (requires email scope) | |
| name | string | Display name (requires profile scope) |
| tid | string | Tenant ID the user belongs to |
| roles | string[] | User roles (requires roles scope) |
Example decoded payload:
{
"sub": "usr_01H8ABC123",
"iss": "https://id.vyntech.com.au",
"aud": "cli_01H8XYZ789",
"exp": 1700000900,
"iat": 1700000000,
"nonce": "n-0S6_WzA2Mj",
"email": "jane@acme-corp.com",
"name": "Jane Smith",
"tid": "tnt_01H7XYZ789",
"roles": ["admin", "member"]
}Best Practices
Always use PKCE
PKCE is required for all clients — public and confidential. Even if your app has a client secret, PKCE provides an additional layer of protection against code interception.
Validate state parameter
Generate a cryptographically random state value before redirecting. On callback, verify it matches to prevent CSRF attacks against your redirect endpoint.
Verify id_token signature
Always verify the EdDSA signature using the public keys from /.well-known/jwks.json. Never trust claims from an unverified token. Cache the JWKS and refresh periodically.
Use codes immediately
Authorization codes expire in 60 seconds and can only be used once. Exchange them for tokens as soon as your callback receives them. Replay attempts are logged and flagged.
What's Next
Public Endpoints (JWKS & Discovery) →
JWKS, OIDC discovery, branding, and password policy endpoints.
Authentication Flows →
Visual diagrams of login, MFA, token refresh, and session lifecycle.
gRPC Integration →
High-performance server-to-server integration via gRPC.
GraphQL Integration →
Flexible querying for frontend apps via the GraphQL API.