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
| Part | Name | Purpose | Example content |
|---|---|---|---|
| 1 | Header | Declares the algorithm and token type | {"alg":"HS256","typ":"JWT"} |
| 2 | Payload | Holds the actual user data | {"username":"zhang_san","user_id":"001",...} |
| 3 | Signature | Signs the first two parts with the secret key to ensure they have not been tampered with | SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
2. Quick Start
2.1 What the Customer Needs to Prepare
| Configuration item | Description | Example value |
|---|---|---|
| SSO shared secret | The key used to sign JWTs; both sides must use the same value | your_real_shared_secret |
| OntiCards API URL | Our SSO login endpoint | https://[OntiCards backend] actual ip:port (or domain) |
| Redirect URL | The frontend page to redirect to after a successful login | https://[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:
| Field | Type | Required | Description |
|---|---|---|---|
username | string | ✅ | Unique identifier of the user; cannot be empty |
user_id | string | ✅ | User ID in the customer system; cannot be empty |
nickname | string | ❌ | User display name |
email | string | ❌ | User email |
source | string | ❌ | Origin identifier used to distinguish systems; defaults to default |
iat | number | ❌ | Token issue time (Unix timestamp) |
exp | number | ✅ | Token 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:
- Receives the JWT Token → reads the
tokenparameter from the URL
- Parses the Header → obtains the algorithm information
- Verifies the signature → uses the shared secret to check whether the token has been tampered with
- Checks expiry → verifies that
expis still valid
- Extracts the Payload → reads user information such as
usernameanduser_id
- Looks up the user → searches for an existing user by
idp_user_id+idp_source
- Creates or links → new users are created automatically; existing users are linked to the login
- Issues a Token → generates OntiCards' own login token
- Redirects → redirects to
redirect_urlwith the new token
8. Error Codes
| HTTP status | error field | Reason |
|---|---|---|
| 400 | missing token parameter | No token was passed in the URL |
| 400 | token is missing required user info | username or user_id is empty in the Payload |
| 401 | token has expired | The exp of the Token has passed |
| 401 | invalid token | Signature verification failed (mismatched secret or tampered content) |
9. Configuration Summary
9.1 Provided by OntiCards
| Item | Value | Purpose |
|---|---|---|
| SSO login endpoint | https://api.onticards.com{OntiCards API地址}/sso/login | The URL the customer redirects to |
| SSO shared secret | Provided by the customer, stored by us | Used to verify the JWT signature |
9.2 Provided by the Customer
| Item | Example value | Description |
|---|---|---|
| Shared secret | K7x#9mP$2nL5@qR8 | A random string of 64+ characters is recommended |
| Redirect URL | https://frontend.onticards.com/overview | The 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.