OAuth 2.0

Toki Sign in — Developer Docs

Connecting to TOKI OAuth2 with Authorization Code Flow (with PKCE) for secure user authentication.

Authorization Code Flow with PKCE

This guide explains how to configure your application to authenticate users to TOKI, implementing the Authorization Code Flow with PKCE for enhanced security.

GEThttps://sso.toki.mn/oauth2/authorize
POSThttps://sso.toki.mn/oauth2/token
GEThttps://sso.toki.mn/oauth2/userinfo

Before You Start

Client Application Registration

Register your application with the OAuth2 provider and obtain the following details:

Field Description
client_id required Your application's client identifier
redirect_uri required The URL to which the user will be redirected after successful authentication
client_secret Required only for flows without PKCE

OAuth2 Flows

1. Authorization Code Flow with PKCE

Route Example
/oauth2/authorize ?response_type=code &client_id=<your_client_id> &redirect_uri=<your_redirect_uri> &code_challenge=<code_challenge> &code_challenge_method=S256 &scope=<scope> &state=<state>

2. Authorization Code Flow (without PKCE)

Route Example
/oauth2/authorize ?response_type=code &client_id=<your_client_id> &redirect_uri=<your_redirect_uri> &scope=<scope> &state=<state>

Implementation Steps


1

Redirect User to Authorization Endpoint

a) With PKCE

GEThttps://sso.toki.mn/oauth2/authorize
Parameter Value
response_type code
client_id your client id
redirect_uri your redirect uri
code_challenge base64url-encoded SHA256 hash of code_verifier
code_challenge_method S256
scope space separated list of scopes
state optional random string
Example URL (with PKCE)
https://sso.toki.mn/oauth2/authorize?client_id=your_client_id&code_challenge=example&code_challenge_method=S256&redirect_uri=https://your.app/callback&response_type=code&scope=offline_access&state=example

b) Without PKCE

Parameter Value
response_type code
client_id your client id
redirect_uri your redirect uri
scope space separated list of scopes
state optional random string
Example URL (without PKCE)
https://sso.toki.mn/oauth2/authorize?client_id=your_client_id&redirect_uri=https://your.app/callback&response_type=code&scope=offline_access&state=example

2

Handle Redirect with Authorization Code

After the user authenticates, they will be redirected to your redirect_uri with the following query parameters:

Parameter Description
code The authorization code
state (If sent) The state parameter to prevent CSRF
Example Redirect
https://yourapp.com/callback?code=abc123&state=x3YVOXHsAAA5a_2zyPyR9_JVrkT3Kxpz4sJf59-8L-4

3

Exchange Authorization Code for Access Token

POSThttps://sso.toki.mn/oauth2/token

a) With PKCE

Request Body
Content-Type: application/x-www-form-urlencoded grant_type=authorization_code code=<authorization-code> redirect_uri=<your-redirect-uri> client_id=<your-client-id> code_verifier=<original-code-verifier>

b) Without PKCE (uses client_secret)

Request Body
Content-Type: application/x-www-form-urlencoded grant_type=authorization_code code=<authorization-code> redirect_uri=<your-redirect-uri> client_id=<your-client-id> client_secret=<your-client-secret>

4

Parse the Token Response

The response will be a JSON object containing:

Field Description
access_token The token to access protected resources
token_type Typically Bearer
expires_in Token expiration time in seconds
refresh_token (Optional) A token to obtain a new access token
id_token (Optional) JWT containing user claims if openid scope is used
Example Response
{ "access_token": "xyz456", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "abc789", "id_token": "eyJhbGciOiJIUzI1..." }

5

Get User Info

Send a GET request to the userinfo endpoint with the access token in the Authorization header:

GEThttps://sso.toki.mn/oauth2/userinfo?userId=<user_id>
Example cURL
curl -L 'https://sso.toki.mn/oauth2/userinfo?userId=<user_id>' \ -H 'Authorization: Bearer <access_token>'

Replace <user_id> with the userId from the /token API response.

Authorization Code Flow with PKCE — Node.js

require('dotenv').config();
const express = require('express');
const axios   = require('axios');
const crypto  = require('crypto');

const app  = express();
const port = 8080;

const client_id      = process.env.CLIENT_ID;
const redirect_uri   = process.env.REDIRECT_URI;
const auth_endpoint  = process.env.AUTH_ENDPOINT;
const token_endpoint = process.env.TOKEN_ENDPOINT;

let code_verifier = '';
let state = '';

function base64URLEncode(str) {
  return str.toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

function generateCodeVerifier() {
  return base64URLEncode(crypto.randomBytes(32));
}

function generateCodeChallenge(code_verifier) {
  return base64URLEncode(
    crypto.createHash('sha256').update(code_verifier).digest()
  );
}

// Start auth flow
app.get('/login', async (req, res) => {
  code_verifier = generateCodeVerifier();
  const code_challenge = generateCodeChallenge(code_verifier);
  state = crypto.randomBytes(16).toString('hex');

  const authUrl = `${auth_endpoint}?`
    + `client_id=${encodeURIComponent(client_id)}`
    + `&response_type=code`
    + `&redirect_uri=${encodeURIComponent(redirect_uri)}`
    + `&code_challenge=${encodeURIComponent(code_challenge)}`
    + `&code_challenge_method=S256`
    + `&scope=openid offline_access`
    + `&state=${state}`;

  await open(authUrl);
  res.send('Redirecting to TOKI for authentication...');
});

// Callback handler
app.get('/callback', async (req, res) => {
  const { code, state: returnedState } = req.query;

  if (returnedState !== state)
    return res.status(400).send('State mismatch!');

  const params = new URLSearchParams();
  params.append('grant_type', 'authorization_code');
  params.append('code', code);
  params.append('redirect_uri', redirect_uri);
  params.append('client_id', client_id);
  params.append('code_verifier', code_verifier);

  const tokenResponse = await axios.post(token_endpoint, params, {
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
  });

  res.json(tokenResponse.data);
});

app.listen(port, () => console.log(`App running at http://localhost:${port}`));

Authorization Code Flow with Client Secret — Node.js

require('dotenv').config();
const express = require('express');
const axios   = require('axios');
const crypto  = require('crypto');

const app  = express();
const port = 8080;

const client_id     = process.env.CLIENT_ID;
const client_secret = process.env.CLIENT_SECRET;
const redirect_uri  = process.env.REDIRECT_URI;
const auth_endpoint  = process.env.AUTH_ENDPOINT;
const token_endpoint = process.env.TOKEN_ENDPOINT;

let state = '';

// Start auth flow
app.get('/login', async (req, res) => {
  state = crypto.randomBytes(16).toString('hex');

  const authUrl = `${auth_endpoint}?`
    + `client_id=${encodeURIComponent(client_id)}`
    + `&response_type=code`
    + `&redirect_uri=${encodeURIComponent(redirect_uri)}`
    + `&scope=openid offline_access`
    + `&state=${state}`;

  await open(authUrl);
  res.send('Redirecting to TOKI for authentication...');
});

// Callback handler
app.get('/callback', async (req, res) => {
  const { code, state: returnedState } = req.query;

  if (returnedState !== state)
    return res.status(400).send('State mismatch!');

  const params = new URLSearchParams();
  params.append('grant_type', 'authorization_code');
  params.append('code', code);
  params.append('redirect_uri', redirect_uri);
  params.append('client_id', client_id);
  params.append('client_secret', client_secret);

  const tokenResponse = await axios.post(token_endpoint, params, {
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
  });

  res.json(tokenResponse.data);
});

app.listen(port, () => console.log(`App running at http://localhost:${port}`));