Docs/Account/Integration/OAuth 2.1 / OIDC

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.

ChangeOAuth 2.0OAuth 2.1 (Vyntech)
PKCEOptional (public clients only)Required for all clients
Implicit flowAllowedRemoved
ROPC grantAllowedRemoved
Refresh token rotationRecommendedEnforced

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.

GET/.well-known/openid-configurationPublic

OpenID Connect Discovery document. Returns all supported endpoints, scopes, and algorithms.

curl https://id.vyntech.com.au/.well-known/openid-configuration

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.

1

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(/=+$/, "");
2

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 ID
redirect_uriMust match a registered redirect URI
scopeSpace-separated list of requested scopes
stateRandom value to prevent CSRF (verified on callback)
code_challengeBase64-URL-encoded SHA-256 of code_verifier
code_challenge_methodAlways "S256"
3

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.

4

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.

5

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.

POST/oauth/tokenPublic

Exchange authorization code for access, ID, and refresh tokens.

curl -X POST https://id.vyntech.com.au/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=AUTH_CODE_HERE" \ -d "redirect_uri=https://yourapp.com/callback" \ -d "client_id=YOUR_CLIENT_ID" \ -d "code_verifier=YOUR_CODE_VERIFIER"
6

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.

ScopeClaims / EffectRequired
openidEnables OIDC — returns id_token with sub, iss, aud, exp, iatYes
profileAdds display_name to id_token and userinfoNo
emailAdds email and email_verified to id_token and userinfoNo
offline_accessIssues a refresh_token alongside the access tokenNo
rolesAdds roles array to id_token and access tokenNo

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.

POST/oauth/tokenPublic

Exchange authorization code for tokens (grant_type=authorization_code).

curl -X POST https://id.vyntech.com.au/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=AUTH_CODE_HERE" \ -d "redirect_uri=https://yourapp.com/callback" \ -d "client_id=YOUR_CLIENT_ID" \ -d "code_verifier=YOUR_CODE_VERIFIER"
Request Body
{
  "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.

GET/oauth/userinfo🔒 Auth

Get the current user's profile claims using a valid access token.

curl https://id.vyntech.com.au/oauth/userinfo \ -H "Authorization: Bearer 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.

POST/oauth/tokenPublic

Refresh an access token using a valid refresh token (grant_type=refresh_token).

curl -X POST https://id.vyntech.com.au/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "refresh_token=rt_01H8XYZABC123..." \ -d "client_id=YOUR_CLIENT_ID"

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.

POST/oauth/clients🔒 Auth

Register a new OAuth client for your tenant.

curl -X POST https://id.vyntech.com.au/oauth/clients \ -H "Authorization: Bearer ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "My Web App", "redirect_uris": [ "https://myapp.com/callback", "http://localhost:3000/callback" ], "scopes": ["openid", "profile", "email", "offline_access", "roles"] }'
Request Body
{
  "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.

ClaimTypeDescription
substringUser ID (unique identifier)
issstringIssuer — always https://id.vyntech.com.au
audstringAudience — your client_id
expnumberExpiration time (Unix timestamp)
iatnumberIssued-at time (Unix timestamp)
noncestringEchoed from authorization request (if provided)
emailstringUser email (requires email scope)
namestringDisplay name (requires profile scope)
tidstringTenant ID the user belongs to
rolesstring[]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

We use cookies and similar technologies to measure traffic and improve the site. You can choose which categories to allow. Manage Preferences.