Total Access Docs
API Reference

OAuth2

OAuth2 authorization flows, token management, and OpenID Connect userinfo.

OAuth2 Provider

TotalAccess implements an OAuth2 authorization server, allowing third-party applications to securely access the TotalAccess API on behalf of a user.

Supported Flows

FlowUse CaseUser Context
Authorization Code + PKCEWeb apps, mobile apps, SPAsYes
Client CredentialsServer-to-server, background jobsNo

Endpoints

EndpointMethodDescription
/api/v1/oauth/authorizeGETAuthorization endpoint — redirects user to consent screen
/api/v1/oauth/authorizePOSTConsent decision — approve or deny authorization
/api/v1/oauth/tokenPOSTToken endpoint — exchange code, refresh token, or client credentials
/api/v1/oauth/revokePOSTToken revocation (RFC 7009)
/api/v1/oauth/userinfoGETOIDC UserInfo endpoint

Scopes

OAuth2 scopes are identical to API key scopes. See the API Keys page for the full list.

The openid, profile, and email scopes are available for OIDC-compatible flows.

Authorization Code Flow with PKCE

Step 1 — Register an OAuth Application

Before using OAuth2, you need to register an application to obtain a client_id and client_secret. This is done via the developer portal or API.

Step 2 — Build the Authorization URL

Redirect the user to the authorization endpoint:

GET /api/v1/oauth/authorize

Parameters:

ParameterRequiredDescription
response_typeYesMust be code
client_idYesYour OAuth application client ID
redirect_uriYesMust match a registered redirect URI
scopeYesSpace-separated list of requested scopes
stateRecommendedRandom string for CSRF protection
code_challengeYes (PKCE)SHA-256 hash of the code verifier, base64url-encoded
code_challenge_methodYes (PKCE)Must be S256

Example URL:

https://app.totalaccess.co.za/api/v1/oauth/authorize?
  response_type=code&
  client_id=ta_oa_abc123&
  redirect_uri=https://myapp.com/callback&
  scope=employees:read%20attendance:read&
  state=randomstate123&
  code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSst-cTY&
  code_challenge_method=S256

The user will be redirected to a branded consent screen showing:

  • Application name and logo
  • Requested scopes with descriptions
  • Authorize / Deny buttons

If the user is not logged in, they will be redirected to the login page first, then back to the consent screen.

Step 4 — Handle the Redirect

After the user approves or denies, TotalAccess redirects to your redirect_uri:

On approval:

https://myapp.com/callback?code=ta_ac_xxx&state=randomstate123

On denial:

https://myapp.com/callback?error=access_denied&error_description=The+user+denied+the+authorization+request&state=randomstate123

Always verify the state parameter matches what you sent. This prevents CSRF attacks.

Step 5 — Exchange the Code for Tokens

POST /api/v1/oauth/token

The token endpoint accepts both application/x-www-form-urlencoded and application/json content types. Client credentials can also be passed via HTTP Basic authentication (Authorization: Basic base64(client_id:client_secret)).

Parameters:

ParameterRequiredDescription
grant_typeYesauthorization_code
codeYesThe authorization code from step 4
redirect_uriYesMust match the one from step 2
client_idYesYour client ID (or via Basic auth)
client_secretYesYour client secret (or via Basic auth)
code_verifierYes (PKCE)The original code verifier used to generate the challenge

Example:

curl -X POST https://app.totalaccess.co.za/api/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=ta_ac_xxx" \
  -d "redirect_uri=https://myapp.com/callback" \
  -d "client_id=ta_oa_abc123" \
  -d "client_secret=ta_os_secret123" \
  -d "code_verifier=dBjftJeZbCkVYyoWjKqYJgkKqKqKqKqKqKqKqKqKqK"

Or using JSON body:

curl -X POST https://app.totalaccess.co.za/api/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "ta_ac_xxx",
    "redirect_uri": "https://myapp.com/callback",
    "client_id": "ta_oa_abc123",
    "client_secret": "ta_os_secret123",
    "code_verifier": "dBjftJeZbCkVYyoWjKqYJgkKqKqKqKqKqKqKqKqKqK"
  }'

Or using HTTP Basic authentication:

curl -X POST https://app.totalaccess.co.za/api/v1/oauth/token \
  -u "ta_oa_abc123:ta_os_secret123" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=ta_ac_xxx" \
  -d "redirect_uri=https://myapp.com/callback" \
  -d "code_verifier=dBjftJeZbCkVYyoWjKqYJgkKqKqKqKqKqKqKqKqKqK"
const response = await fetch('https://app.totalaccess.co.za/api/v1/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: 'ta_ac_xxx',
    redirect_uri: 'https://myapp.com/callback',
    client_id: 'ta_oa_abc123',
    client_secret: 'ta_os_secret123',
    code_verifier: 'dBjftJeZbCkVYyoWjKqYJgkKqKqKqKqKqKqKqKqKqK',
  }),
});

const tokens = await response.json();

Response (200 OK):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "ta_rt_xxx",
  "scope": "employees:read attendance:read"
}

Step 6 — Use the Access Token

GET /api/v1/employees
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Refreshing Tokens

Refresh tokens are rotated on each use — you receive a new refresh token with every refresh. If a refresh token is used twice, the entire token chain is revoked (reuse detection).

POST /api/v1/oauth/token

Parameters:

ParameterRequiredDescription
grant_typeYesrefresh_token
refresh_tokenYesThe refresh token from the previous response
client_idYesYour client ID
client_secretYesYour client secret (for confidential clients)

Example:

curl -X POST https://app.totalaccess.co.za/api/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=ta_rt_xxx" \
  -d "client_id=ta_oa_abc123" \
  -d "client_secret=ta_os_secret123"

Always store the new refresh_token from the response. The previous one is invalidated immediately.

Client Credentials Flow

For server-to-server integrations without user context:

POST /api/v1/oauth/token

Parameters:

ParameterRequiredDescription
grant_typeYesclient_credentials
client_idYesYour client ID
client_secretYesYour client secret
scopeOptionalRequested scopes

Example:

curl -X POST https://app.totalaccess.co.za/api/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=ta_oa_abc123" \
  -d "client_secret=ta_os_secret123" \
  -d "scope=employees:read"

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "employees:read"
}

Client credentials tokens do not include a refresh token. Request a new token when the access token expires.

Token Revocation

Revoke an access or refresh token (RFC 7009):

POST /api/v1/oauth/revoke

Parameters:

ParameterRequiredDescription
tokenYesThe token to revoke
token_type_hintOptionalaccess_token or refresh_token

Example:

curl -X POST https://app.totalaccess.co.za/api/v1/oauth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=ta_rt_xxx" \
  -d "token_type_hint=refresh_token"

The revocation endpoint also accepts application/json content type. Client authentication is not required — the endpoint revokes tokens based solely on the token parameter, as per RFC 7009.

The endpoint returns 200 OK regardless of whether the token was valid (to prevent information leakage).

UserInfo Endpoint (OIDC)

Retrieve profile information about the authenticated user:

GET /api/v1/oauth/userinfo
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response (user-context tokens, based on granted scopes):

{
  "sub": "123",
  "name": "John Doe",
  "given_name": "John",
  "family_name": "Doe",
  "picture": "https://cdn.totalaccess.co.za/avatars/123.jpg",
  "email": "[email protected]",
  "email_verified": true
}

Response (client_credentials tokens, no user context):

{
  "sub": "ta_oa_abc123",
  "client_id": "ta_oa_abc123",
  "scope": "employees:read"
}
FieldScope Required
subopenid
email, email_verifiedemail (or profile)
name, given_name, family_name, pictureprofile

Token Lifetimes

Token TypeLifetime
Authorization code10 minutes
Access token15 minutes
Refresh token30 days

PKCE Implementation

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. It is required for all authorization code flows.

Generating the Code Verifier and Challenge

import crypto from 'crypto';

// 1. Generate a random code verifier (43-128 chars)
const codeVerifier = crypto.randomBytes(32).toString('base64url');

// 2. Compute the code challenge (S256)
const codeChallenge = crypto
  .createHash('sha256')
  .update(codeVerifier)
  .digest('base64url');
// 1. Generate a random code verifier
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const codeVerifier = btoa(String.fromCharCode(...array))
  .replace(/\+/g, '-')
  .replace(/\//g, '_')
  .replace(/=+$/, '');

// 2. Compute the code challenge
const encoded = new TextEncoder().encode(codeVerifier);
const hash = await crypto.subtle.digest('SHA-256', encoded);
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(hash)))
  .replace(/\+/g, '-')
  .replace(/\//g, '_')
  .replace(/=+$/, '');

Using OAuth2 Tokens with API Routes

API routes that support OAuth2 token authentication will accept Bearer tokens from OAuth2 in the Authorization header. The route must explicitly enable allowOAuthTokenAuth:

const authResult = await authenticateAndGetClient(request, {
  allowOAuthTokenAuth: true,
  requiredScopes: ['employees:read'],
});

The middleware verifies:

  1. JWT signature and expiry
  2. Token exists in the database and is not revoked
  3. Granted scopes satisfy the required scopes
  4. User still exists (for user-context tokens)

On this page