Docs / OntiCards API SSO Single Sign-On Integration Guide (JWT Mode)

OntiCards API SSO Single Sign-On Integration Guide (JWT Mode)

Overview

This document describes how a third-party system can integrate with the OntiCards single sign-on (SSO) service using JWT tokens.

1. What Is a JWT Token

JWT (JSON Web Token) is an open standard (RFC 7519) for securely transmitting information between parties. A JWT consists of three parts, separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InpoYW5nX3NhbiIsInVzZXJfaWQiOiJTWVNfVVNFUl8wMDEifQ.signature
|______________|.|__________________________________________|.______________|
    Header      |              Payload                      |   Signature
PartNamePurposeExample content
1HeaderDeclares the algorithm and token type{"alg":"HS256","typ":"JWT"}
2PayloadHolds the actual user data{"username":"zhang_san","user_id":"001",...}
3SignatureSigns the first two parts with the secret key to ensure they have not been tampered withSflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

2. Quick Start

2.1 What the Customer Needs to Prepare

Configuration itemDescriptionExample value
SSO shared secretThe key used to sign JWTs; both sides must use the same valueyour_real_shared_secret
OntiCards API URLOur SSO login endpointhttps://[OntiCards backend] actual ip:port (or domain)
Redirect URLThe frontend page to redirect to after a successful loginhttps://[OntiCards frontend] actual ip:port (or domain)/overview

2.2 Overall Flow

1. The customer's backend generates a JWT Token
   ↓
2. Build the SSO login URL (with token and redirect_url)
   ↓
3. The user's browser redirects to our SSO endpoint
   ↓
4. We verify the JWT, create/link the user, and generate our own Token
   ↓
5. The browser redirects to redirect_url with our Token
   ↓
6. The customer's frontend receives the Token; login is complete

3. Generating the Token (must be done by the customer's backend)

3.1 Part 1: Header

The header is fixed-format and declares that the HS256 algorithm is used:

{
    "alg": "HS256",
    "typ": "JWT"
}

This JSON object is then encoded with Base64URL.

3.2 Part 2: Payload (user data)

This is the most important part and carries the user information to be passed:

{
    "username": "zhang_san",
    "user_id": "SYS_USER_001",
    "nickname": "张三",
    "email": "zhangsan@example.com",
    "source": "your_app",
    "iat": 1713000000,
    "exp": 1713000600
}

Field descriptions:

FieldTypeRequiredDescription
usernamestringUnique identifier of the user; cannot be empty
user_idstringUser ID in the customer system; cannot be empty
nicknamestringUser display name
emailstringUser email
sourcestringOrigin identifier used to distinguish systems; defaults to default
iatnumberToken issue time (Unix timestamp)
expnumberToken expiry time (Unix timestamp); it is recommended to set this to 5 minutes from now

This JSON object is then encoded with Base64URL.

3.3 Part 3: Signature

Join the first two parts with a dot, then sign the resulting string with the shared secret:

Signature string = Header_base64 + "." + Payload_base64
Signature = HMAC-SHA256(Signature string, shared secret)

3.4 The Final JWT Token

JWT Token = Header_base64 + "." + Payload_base64 + "." + Signature_base64

4. Generating JWTs in Different Languages

4.1 Python Example

import jwt
from datetime import datetime, timedelta, timezone

# Shared secret configured by the customer (must be provided to OntiCards)
SECRET_KEY = "your_shared_secret_key"

# Payload (user data)
payload = {
    "username": "zhang_san",
    "user_id": "SYS_USER_001",
    "nickname": "张三",
    "email": "zhangsan@example.com",
    "source": "your_app",
    "iat": datetime.now(timezone.utc),
    "exp": datetime.now(timezone.utc) + timedelta(minutes=5)  # Expires in 5 minutes
}

# Generate the JWT Token
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print(token)
# Output looks like: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InpoYW5nX3NhbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

4.2 Java Example

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;

String secretKey = "your_shared_secret_key";

Map<String, Object> payload = Map.of(
    "username", "zhang_san",
    "user_id", "SYS_USER_001",
    "nickname", "张三",
    "email", "zhangsan@example.com",
    "source", "your_app"
);

String token = Jwts.builder()
    .claims(payload)
    .issuedAt(new Date())
    .expiration(new Date(System.currentTimeMillis() + 5 * 60 * 1000)) // Expires in 5 minutes
    .signWith(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)))
    .compact();

System.out.println(token);

4.3 Node.js Example

const jwt = require('jsonwebtoken');

const secretKey = 'your_shared_secret_key';

const payload = {
    username: 'zhang_san',
    user_id: 'SYS_USER_001',
    nickname: '张三',
    email: 'zhangsan@example.com',
    source: 'your_app'
};

const token = jwt.sign(payload, secretKey, {
    algorithm: 'HS256',
    expiresIn: '5m'  // Expires in 5 minutes
});

console.log(token);

4.4 Generating in the Frontend (for testing only)

// ⚠️ For testing only; in production, the Token MUST be generated on the backend!

async function generateJWT(payload, secret) {
    // Header
    const header = { "alg": "HS256", "typ": "JWT" };
    const headerB64 = btoa(JSON.stringify(header))
        .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
    
    // Payload
    const payloadB64 = btoa(JSON.stringify(payload))
        .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
    
    // Signature
    const signatureInput = headerB64 + '.' + payloadB64;
    const encoder = new TextEncoder();
    const key = await crypto.subtle.importKey(
        'raw', encoder.encode(secret),
        { name: 'HMAC', hash: 'SHA-256' },
        false, ['sign']
    );
    const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(signatureInput));
    const signatureB64 = btoa(String.fromCharCode(...new Uint8Array(signature)))
        .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
    
    return signatureInput + '.' + signatureB64;
}

// Usage example
const payload = {
    username: 'test_user',
    user_id: 'TEST_001',
    nickname: '测试用户',
    exp: Math.floor(Date.now() / 1000) + 300  // Expires in 5 minutes
};
generateJWT(payload, 'your_secret_key').then(token => console.log(token));

5. Building the Login URL and Redirecting

5.1 Construct the URL

{OntiCards API URL}/sso/login?token={JWT Token}&redirect_url={callback URL}

Example:

https://api.onticards.com/sso/login?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&redirect_url=https://your-app.com/dashboard

5.2 Redirect Methods

Method 1: Direct redirect (recommended)

// Redirect after generating the Token
const ssoUrl = `${API_BASE}/sso/login?token=${encodeURIComponent(token)}&redirect_url=${encodeURIComponent(FRONTEND_URL)}`;
window.location.href = ssoUrl;

Method 2: Open in a new window

window.open(`${API_BASE}/sso/login?token=${encodeURIComponent(token)}`, '_blank');

6. Receiving the Token at the Redirect URL

After a successful login, the browser is redirected to:

https://frontend.onticards.com/overview?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

6.1 Frontend Code to Receive the Token

// Read access_token from the URL
function getAccessToken() {
    const params = new URLSearchParams(window.location.search);
    return params.get('access_token');
}

// Store the Token
const token = getAccessToken();
if (token) {
    localStorage.setItem('access_token', token);

    // Optional: parse the Token to get user info (not recommended for security checks; display only)
    const parts = token.split('.');
    if (parts.length === 3) {
        const payload = JSON.parse(atob(parts[1]));
        console.log('Logged in user:', payload.nickname || payload.username);
        console.log('User role:', payload.role);
    }

    // Clean the token from the URL (avoid token leakage in browser history)
    window.history.replaceState({}, document.title, window.location.pathname);
}

6.2 Carrying the Token in Subsequent Requests

// Carry the Token in the header for subsequent API requests
fetch('/api/your-endpoint', {
    headers: {
        'Authorization': 'Bearer ' + localStorage.getItem('access_token')
    }
});

7. User Creation / Login Logic

When a user accesses OntiCards through SSO for the first time, the system:

  1. Receives the JWT Token → reads the token parameter from the URL
  1. Parses the Header → obtains the algorithm information
  1. Verifies the signature → uses the shared secret to check whether the token has been tampered with
  1. Checks expiry → verifies that exp is still valid
  1. Extracts the Payload → reads user information such as username and user_id
  1. Looks up the user → searches for an existing user by idp_user_id + idp_source
  1. Creates or links → new users are created automatically; existing users are linked to the login
  1. Issues a Token → generates OntiCards' own login token
  1. Redirects → redirects to redirect_url with the new token

8. Error Codes

HTTP statuserror fieldReason
400missing token parameterNo token was passed in the URL
400token is missing required user infousername or user_id is empty in the Payload
401token has expiredThe exp of the Token has passed
401invalid tokenSignature verification failed (mismatched secret or tampered content)

9. Configuration Summary

9.1 Provided by OntiCards

ItemValuePurpose
SSO login endpointhttps://api.onticards.com{OntiCards API地址}/sso/loginThe URL the customer redirects to
SSO shared secretProvided by the customer, stored by usUsed to verify the JWT signature

9.2 Provided by the Customer

ItemExample valueDescription
Shared secretK7x#9mP$2nL5@qR8A random string of 64+ characters is recommended
Redirect URLhttps://frontend.onticards.com/overviewThe frontend page to redirect to after a successful login

9.3 Environment Variable Configuration

Configure on the OntiCards server:

SSO_SECRET_KEY=shared secret provided by the customer

10. Testing and Verification

10.1 Test Pages

OntiCards provides local test pages:

  • SSO test center: http://localhost:9103/static/sso_test.html
  • Callback test page: http://localhost:9103/static/sso_callback.html

10.2 Integration Checklist

  • Generate a JWT Token (verify the three-part structure: Header.Payload.Signature)
  • The Payload contains the required fields: username, user_id, exp
  • The Token is signed with the HS256 algorithm
  • Build the SSO login URL
  • Test the redirect flow
  • Verify that the callback page receives access_token
  • Confirm the Token validity period (5 minutes is recommended)
  • Use a strong secret in production; do not use the example secret

11. Contact

If you have any questions, please contact OntiCards technical support.