Secure token provider for SuperApp Partner Apps
npm install @superapp_men/token-providerSecure token management for SuperApp Partner Apps. Get authentication tokens from the SuperApp without accessing sensitive APIs directly.
✅ Secure - Partner Apps never handle authentication directly
✅ Simple - Single function call to get tokens
✅ TypeScript - Full type safety and IntelliSense
✅ Cross-Platform - Works on Web, iOS, and Android
✅ Zero Dependencies - Lightweight and efficient
``bash`
npm install @superapp_men/token-provider
or
`bash`
yarn add @superapp_men/token-provider
`typescript
import { TokenProvider } from "@superapp_men/token-provider";
const provider = new TokenProvider();
// Get default access token
const token = await provider.getToken();
console.log(token.token); // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
console.log(token.tokenType); // "Bearer"
`
`typescript
const provider = new TokenProvider({
debug: true,
});
// Listen to token events
provider.on("tokenReceived", ({ token }) => {
console.log("Token received from SuperApp");
console.log("Token:", token.token);
});
provider.on("error", ({ code, message }) => {
console.error("Token error:", code, message);
});
provider.on("stateChange", ({ state, previousState }) => {
console.log(State: ${previousState} → ${state});
});
// Get token
const token = await provider.getToken();
`
`typescript
const provider = new TokenProvider();
// Get user info
const userInfo = await provider.getUserInfo();
console.log(userInfo.id); // "user-123"
console.log(userInfo.firstName); // "Hishame"
console.log(userInfo.grade); // 5
`
`typescript
interface TokenProviderConfig {
// Default timeout for requests (default: 5000ms)
timeout?: number;
// Enable debug logging (default: false)
debug?: boolean;
}
`
`typescript`
const provider = new TokenProvider({
timeout: 10000, // 10 seconds
debug: true, // Debug logging enabled
});
#### Constructor
`typescript`
new TokenProvider(config?: TokenProviderConfig)
#### Methods
##### getToken(options?: TokenRequestOptions): Promise
Get a token from the SuperApp. Each call requests a fresh token from the SuperApp. The package returns the token as-is; token validation should be performed in your partner app using JWKS.
Options:
`typescript`
interface TokenRequestOptions {
type?: TokenType; // Token type (default: ACCESS)
timeout?: number; // Request timeout
}
Returns:
`typescript`
interface TokenResponse {
token: string; // The actual token
tokenType: string; // "Bearer", etc.
}
##### getUserInfo(): Promise
Get current user information.
Returns:
`typescript`
interface UserInfo {
id: string;
firstName?: string;
grade?: number;
}
##### getState(): TokenProviderState
Get the current provider state.
Returns: 'idle' | 'requesting' | 'ready' | 'error'
##### on
Add an event listener. Returns unsubscribe function.
##### off
Remove an event listener.
##### removeAllListeners(event?: TokenEventType): void
Remove all event listeners.
##### setDebug(enabled: boolean): void
Enable or disable debug logging.
##### destroy(): void
Cleanup and destroy the provider.
The getToken() method returns a token from the SuperApp. Token validation should be performed in your partner app using JWKS (JSON Web Key Set).
1. Get Token: Call getToken() to receive the token from SuperApphttps://supper-app.azurewebsites.net/.well-known/jwks.json
2. Fetch JWKS: Fetch public keys from kid
3. Extract Key ID: Extract the (Key ID) from the JWT token headerkid
4. Match Key: Find the corresponding public key from the JWKS using the
5. Validate Signature: Validate the token signature using the matched public key
The JWKS endpoint returns a JSON structure like:
`json`
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "WUjXNSKJU69KU-8Ccc9wSKoGjVnVpx9SeoF_ih6TNyE",
"n": "rTknkikAVBH3tWd8p_KaXKOqHx6wBWcbC6DQzoizLhPnZH1kHynEs3EKvN227okxTKfGdmtQgUrC-C-fk5LztMWYR428sao5TmEuhy-bvoEeReh4oDXUqsG7Vc1ilVtjGS6AVpxZtz9Cph4AonLebEyKWHFM9qsqAsmjZNh8L3rqo_uIzz1DVYFer3a1mckgxv1h_EHOFM7SBwiNL3qR4tvsTQNMdHxW49QNg70r69dYy1mbNpZnBBfP-A0bvFrWVnhY3JXEJ25DelvB7UvjlkY3UODuKnlY9fQCrm1nq_aUUYIJJsQRH3DI4N1h3mKYPafrS9iMYf4vi4u85EjESw",
"e": "AQAB"
}
]
}
⚠️ Important: Token validation should be performed on your backend server, not in the client-side code. Client-side validation can be bypassed and should only be used for display purposes.
#### Backend Validation (Recommended)
`typescript
// Backend API endpoint (Node.js/Express example)
import express from "express";
import jwt from "jsonwebtoken";
import jwksClient from "jwks-rsa";
const app = express();
const client = jwksClient({
jwksUri: "https://supper-app.azurewebsites.net/.well-known/jwks.json",
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
app.post("/api/protected", (req, res) => {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
return res.status(401).json({ error: "No token provided" });
}
jwt.verify(token, getKey, (err, decoded) => {
if (err) {
return res
.status(401)
.json({ error: "Invalid token", details: err.message });
}
// Token is valid, proceed with request
res.json({ message: "Access granted", user: decoded });
});
});
`
#### Client-Side Usage
`typescript
// Frontend: Get token and send to backend
import { TokenProvider } from "@superapp_men/token-provider";
const provider = new TokenProvider();
// Get token from SuperApp
const tokenResponse = await provider.getToken();
// Send token to your backend API
const response = await fetch("https://your-api.com/api/protected", {
method: "POST",
headers: {
Authorization: ${tokenResponse.tokenType} ${tokenResponse.token},
},
});
if (response.ok) {
const data = await response.json();
// Use the validated data
}
`
Fired when a token is received from the SuperApp.
`typescript`
provider.on("tokenReceived", ({ token }) => {
console.log("Token:", token.token);
});
Fired when the provider state changes.
`typescriptState: ${previousState} → ${state}
provider.on("stateChange", ({ state, previousState }) => {
console.log();`
});
Fired when an error occurs.
`typescriptError [${code}]: ${message}
provider.on("error", ({ code, message, details }) => {
console.error();`
});
`typescript
import { useState, useEffect } from "react";
import {
TokenProvider,
type TokenResponse,
} from "@superapp_men/token-provider";
export function useToken() {
const [provider] = useState(() => new TokenProvider());
const [token, setToken] = useState
const [loading, setLoading] = useState(false);
const [error, setError] = useState
useEffect(() => {
const unsubscribe = provider.on("tokenReceived", ({ token }) => {
setToken(token);
setLoading(false);
});
const errorUnsub = provider.on("error", ({ message }) => {
setError(message);
setLoading(false);
});
return () => {
unsubscribe();
errorUnsub();
provider.destroy();
};
}, [provider]);
const getToken = async () => {
setLoading(true);
setError(null);
try {
const token = await provider.getToken();
return token;
} catch (err) {
setError(err.message);
throw err;
}
};
return { token, loading, error, getToken, provider };
}
// Usage
function MyComponent() {
const { token, loading, getToken } = useToken();
useEffect(() => {
getToken();
}, []);
if (loading) return
return (
Token: {token?.token.substring(0, 20)}...
$3
`typescript
import { ref, onMounted, onUnmounted } from "vue";
import { TokenProvider } from "@superapp_men/token-provider";export function useToken() {
const provider = new TokenProvider();
const token = ref(null);
const loading = ref(false);
const error = ref(null);
const getToken = async () => {
loading.value = true;
error.value = null;
try {
token.value = await provider.getToken();
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
onMounted(() => {
provider.on("tokenReceived", ({ token: t }) => {
token.value = t;
});
provider.on("error", ({ message }) => {
error.value = message;
});
});
onUnmounted(() => {
provider.destroy();
});
return { token, loading, error, getToken };
}
`Utility Functions
$3
Decode a JWT token payload (without verification - for display only).
`typescript
import { decodeJWT } from "@superapp_men/token-provider";const token = await provider.getToken();
const payload = decodeJWT(token.token);
console.log(payload.sub); // User ID
console.log(payload.exp); // Expiration
console.log(payload.iat); // Issued at
`Error Handling
`typescript
import { TokenProvider, TokenError } from "@superapp_men/token-provider";const provider = new TokenProvider();
try {
const token = await provider.getToken();
} catch (error) {
switch (error.code) {
case TokenError.TIMEOUT:
console.error("Request timeout");
break;
case TokenError.UNAUTHORIZED:
console.error("Not authorized");
break;
case TokenError.SUPERAPP_NOT_AVAILABLE:
console.error("SuperApp not responding");
break;
case TokenError.TOKEN_EXPIRED:
console.error("Token expired - get a new one");
const newToken = await provider.getToken();
break;
default:
console.error("Unknown error:", error.message);
}
}
`Security Considerations
$3
`typescript
// ❌ Bad - vulnerable to XSS
localStorage.setItem("token", token.token);// ✅ Good - get fresh token when needed
const token = await provider.getToken(); // Fresh from SuperApp
`$3
Ensure all communication happens over HTTPS in production.
$3
The package returns tokens from the SuperApp without validation. You should validate tokens in your partner app using JWKS (JSON Web Key Set) from
https://supper-app.azurewebsites.net/.well-known/jwks.json:- Extract the
kid from the JWT header
- Fetch the corresponding public key from JWKS
- Validate the token signatureImportant: Always validate tokens before using them. For production applications, also validate tokens on your backend server.
$3
Since tokens are not cached, call
getToken() whenever you need a new token. The SuperApp will handle token generation securely.Token Signing: RS256 (RSA256) Algorithm
$3
This package uses RS256 (RSA Signature with SHA-256) for token signing, which is an industry-standard asymmetric cryptographic algorithm. Here's why RS256 was chosen:
#### 1. Asymmetric Key Pair Security
- Private Key: Kept secret by the SuperApp (never shared)
- Public Key: Published via JWKS endpoint (safe to share)
- Only the SuperApp can sign tokens (using private key)
- Anyone can verify tokens (using public key)
- If the public key is compromised, tokens can still be verified but new tokens cannot be forged
#### 2. Key Rotation Support
- SuperApp can rotate keys without breaking existing tokens
- Multiple keys can coexist (identified by
kid in JWT header)
- Partner Apps automatically fetch the correct key from JWKS
- Enables secure key rotation without service interruption#### 3. Industry Standard & Compatibility
- RS256 is the most widely supported JWT signing algorithm
- Recommended by OAuth 2.0 and OpenID Connect specifications
- Supported by all major JWT libraries and platforms
- Better security than symmetric algorithms (HS256) for distributed systems
#### 4. Non-Repudiation
- Each token signature is cryptographically unique
- Cannot be forged without the private key
- Provides strong audit trail and accountability
$3
#### Token Signing Process (SuperApp)
1. Create JWT Structure:
`
Header.Payload.Signature
`2. Header (contains algorithm info):
`json
{
"alg": "RS256",
"typ": "JWT",
"kid": "superapp-key-2025-01"
}
`3. Payload (token claims):
`json
{
"sub": "user-123",
"exp": 1735689600,
"iat": 1735686000,
"aud": "partner-app-id"
}
`4. Signing:
- SuperApp creates:
base64Url(header) + "." + base64Url(payload)
- Signs this string using RSA private key with SHA-256 hash
- Appends signature: header.payload.signature#### Token Verification Process (Partner App)
1. Extract Key ID:
- Decode JWT header to get
kid (Key ID)
- Example: "kid": "superapp-key-2025-01"2. Fetch Public Key:
- Request JWKS from:
https://supper-app.azurewebsites.net/.well-known/jwks.json
- Find key matching the kid from JWKS response3. Verify Signature:
- Recreate:
base64Url(header) + "." + base64Url(payload)
- Use RSA public key to verify the signature
- If signature matches → token is authentic and unmodified#### Cryptographic Flow
`
┌─────────────────────────────────────────────────────────┐
│ SuperApp (Signer) │
├─────────────────────────────────────────────────────────┤
│ 1. Generate RSA Key Pair │
│ - Private Key (SECRET) → Used to SIGN tokens │
│ - Public Key → Published in JWKS │
│ │
│ 2. Create JWT Token │
│ Header: { alg: "RS256", kid: "key-2025-01" } │
│ Payload: { sub, exp, iat, aud, ... } │
│ │
│ 3. Sign Token │
│ signature = RSA_SIGN( │
│ SHA256(header + "." + payload), │
│ private_key │
│ ) │
│ │
│ 4. Return: header.payload.signature │
└─────────────────────────────────────────────────────────┘
│
│ Token (JWT)
▼
┌─────────────────────────────────────────────────────────┐
│ Partner App (Verifier) │
├─────────────────────────────────────────────────────────┤
│ 1. Receive JWT Token │
│ │
│ 2. Extract kid from Header │
│ Decode header → Get "kid": "key-2025-01" │
│ │
│ 3. Fetch Public Key from JWKS │
│ GET /.well-known/jwks.json │
│ Find key where kid = "key-2025-01" │
│ │
│ 4. Verify Signature │
│ RSA_VERIFY( │
│ signature, │
│ SHA256(header + "." + payload), │
│ public_key │
│ ) │
│ │
│ 5. If valid → Token is authentic ✅ │
│ If invalid → Token is forged/rejected ❌ │
└─────────────────────────────────────────────────────────┘
`$3
#### ✅ Integrity Protection
- Any modification to header or payload invalidates the signature
- Detects tampering immediately during verification
#### ✅ Authentication
- Only SuperApp (with private key) can create valid tokens
- Partner Apps can verify authenticity using public key
#### ✅ Key Isolation
- Private key never leaves SuperApp servers
- Public key can be safely shared via JWKS
- Compromise of public key doesn't allow token forgery
#### ✅ Forward Security
- Old tokens remain valid even after key rotation
- New tokens use new keys (identified by different
kid)
- Enables secure key rotation without breaking existing sessions$3
| Algorithm | Type | Security | Key Distribution | Use Case |
| --------- | ---------- | -------- | ------------------- | -------------------------------------------------- |
| RS256 | Asymmetric | High | Public key via JWKS | ✅ Distributed systems (SuperApp architecture) |
| HS256 | Symmetric | Medium | Shared secret | Single service |
| ES256 | Asymmetric | High | Public key via JWKS | Alternative to RS256 |
Why RS256 over HS256?
- HS256 requires sharing a secret key → security risk if compromised
- RS256 uses public/private key pair → public key can be safely shared
- RS256 enables key rotation without re-deploying Partner Apps
Why RS256 over ES256?
- RS256 has broader library support
- RS256 keys are larger but more compatible
- RS256 is the OAuth 2.0 recommended default
$3
1. Always Verify Tokens: Never trust tokens without signature verification
2. Cache JWKS: Fetch JWKS periodically and cache (with TTL) to reduce requests
3. Handle Key Rotation: If
kid not found, refresh JWKS and retry verification
4. Validate Claims: After signature verification, validate exp, aud, iss claims
5. Use HTTPS: Always fetch JWKS over HTTPS to prevent MITM attacks$3
`json
{
"keys": [
{
"kty": "RSA", // Key type: RSA
"use": "sig", // Use: signature
"alg": "RS256", // Algorithm: RS256
"kid": "superapp-key-2025-01", // Key ID (matches JWT header)
"n": "rTknkikAVBH3tWd8p_KaXKOqHx6wBWcbC6DQzoizLhPnZH1kHynEs3EKvN227okxTKfGdmtQgUrC-C-fk5LztMWYR428sao5TmEuhy-bvoEeReh4oDXUqsG7Vc1ilVtjGS6AVpxZtz9Cph4AonLebEyKWHFM9qsqAsmjZNh8L3rqo_uIzz1DVYFer3a1mckgxv1h_EHOFM7SBwiNL3qR4tvsTQNMdHxW49QNg70r69dYy1mbNpZnBBfP-A0bvFrWVnhY3JXEJ25DelvB7UvjlkY3UODuKnlY9fQCrm1nq_aUUYIJJsQRH3DI4N1h3mKYPafrS9iMYf4vi4u85EjESw",
"e": "AQAB" // RSA public exponent
}
]
}
`The
n (modulus) and e (exponent) values together form the RSA public key used for signature verification.Browser Support
- Chrome/Edge 80+
- Firefox 75+
- Safari 14+
- iOS Safari 14+ (via Capacitor)
- Android WebView (via Capacitor)
Troubleshooting
$3
Cause: Partner App is not loaded within SuperApp
Solution: Ensure app is running in iframe or Capacitor WebView
$3
Cause: SuperApp not responding or slow network
Solution: Increase timeout in configuration
`typescript
const provider = new TokenProvider({ timeout: 10000 });
`$3
Cause: Token has expired
Solution: Get a new token by calling
getToken() again`typescript
try {
const token = await provider.getToken();
// Use token...
} catch (error) {
if (error.code === TokenError.TOKEN_EXPIRED) {
// Get a new token
const newToken = await provider.getToken();
}
}
``MIT
- Email: h.afifi@alexsys.solutions
---
Ready to use secure tokens? Install the package and get started! 🔐