@superapp_men/token-provider
v1.0.7
Published
Secure token provider for SuperApp Partner Apps
Maintainers
Readme
@superapp_men/token-provider
Secure token management for SuperApp Partner Apps. Get authentication tokens from the SuperApp without accessing sensitive APIs directly.
Features
✅ 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
Installation
npm install @superapp_men/token-provideror
yarn add @superapp_men/token-providerBasic Usage
Get Access Token
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"Advanced Usage
With Event Listeners
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();Get User Information
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); // 5Configuration
interface TokenProviderConfig {
// Default timeout for requests (default: 5000ms)
timeout?: number;
// Enable debug logging (default: false)
debug?: boolean;
}Example Configuration
const provider = new TokenProvider({
timeout: 10000, // 10 seconds
debug: true, // Debug logging enabled
});API Reference
TokenProvider Class
Constructor
new TokenProvider(config?: TokenProviderConfig)Methods
getToken(options?: TokenRequestOptions): Promise<TokenResponse>
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:
interface TokenRequestOptions {
type?: TokenType; // Token type (default: ACCESS)
timeout?: number; // Request timeout
}Returns:
interface TokenResponse {
token: string; // The actual token
tokenType: string; // "Bearer", etc.
}getUserInfo(): Promise<UserInfo>
Get current user information.
Returns:
interface UserInfo {
id: string;
firstName?: string;
grade?: number;
}getState(): TokenProviderState
Get the current provider state.
Returns: 'idle' | 'requesting' | 'ready' | 'error'
on<T>(event: TokenEventType, callback: EventListener<T>): () => void
Add an event listener. Returns unsubscribe function.
off<T>(event: TokenEventType, callback: EventListener<T>): void
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.
Token Validation
The getToken() method returns a token from the SuperApp. Token validation should be performed in your partner app using JWKS (JSON Web Key Set).
Validation Steps (in your app)
- Get Token: Call
getToken()to receive the token from SuperApp - Fetch JWKS: Fetch public keys from
https://supper-app.azurewebsites.net/.well-known/jwks.json - Extract Key ID: Extract the
kid(Key ID) from the JWT token header - Match Key: Find the corresponding public key from the JWKS using the
kid - Validate Signature: Validate the token signature using the matched public key
JWKS Endpoint
The JWKS endpoint returns a JSON structure like:
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "WUjXNSKJU69KU-8Ccc9wSKoGjVnVpx9SeoF_ih6TNyE",
"n": "rTknkikAVBH3tWd8p_KaXKOqHx6wBWcbC6DQzoizLhPnZH1kHynEs3EKvN227okxTKfGdmtQgUrC-C-fk5LztMWYR428sao5TmEuhy-bvoEeReh4oDXUqsG7Vc1ilVtjGS6AVpxZtz9Cph4AonLebEyKWHFM9qsqAsmjZNh8L3rqo_uIzz1DVYFer3a1mckgxv1h_EHOFM7SBwiNL3qR4tvsTQNMdHxW49QNg70r69dYy1mbNpZnBBfP-A0bvFrWVnhY3JXEJ25DelvB7UvjlkY3UODuKnlY9fQCrm1nq_aUUYIJJsQRH3DI4N1h3mKYPafrS9iMYf4vi4u85EjESw",
"e": "AQAB"
}
]
}Example: Validating Token (Recommended: Backend)
⚠️ 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)
// 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
// 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
}Events
tokenReceived
Fired when a token is received from the SuperApp.
provider.on("tokenReceived", ({ token }) => {
console.log("Token:", token.token);
});stateChange
Fired when the provider state changes.
provider.on("stateChange", ({ state, previousState }) => {
console.log(`State: ${previousState} → ${state}`);
});error
Fired when an error occurs.
provider.on("error", ({ code, message, details }) => {
console.error(`Error [${code}]: ${message}`);
});Complete Examples
React Hook
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<TokenResponse | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
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 <div>Loading...</div>;
return (
<div>
<p>Token: {token?.token.substring(0, 20)}...</p>
<button onClick={getToken}>Get New Token</button>
</div>
);
}Vue Composable
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
decodeJWT(token: string)
Decode a JWT token payload (without verification - for display only).
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 atError Handling
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
1. Never Store Tokens in localStorage
// ❌ Bad - vulnerable to XSS
localStorage.setItem("token", token.token);
// ✅ Good - get fresh token when needed
const token = await provider.getToken(); // Fresh from SuperApp2. Always Use HTTPS
Ensure all communication happens over HTTPS in production.
3. Validate Tokens in Your App
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
kidfrom the JWT header - Fetch the corresponding public key from JWKS
- Validate the token signature
Important: Always validate tokens before using them. For production applications, also validate tokens on your backend server.
4. Get Fresh Tokens When Needed
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
Why RS256?
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
kidin 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
How RS256 Works
Token Signing Process (SuperApp)
Create JWT Structure:
Header.Payload.SignatureHeader (contains algorithm info):
{ "alg": "RS256", "typ": "JWT", "kid": "superapp-key-2025-01" }Payload (token claims):
{ "sub": "user-123", "exp": 1735689600, "iat": 1735686000, "aud": "partner-app-id" }Signing:
- SuperApp creates:
base64Url(header) + "." + base64Url(payload) - Signs this string using RSA private key with SHA-256 hash
- Appends signature:
header.payload.signature
- SuperApp creates:
Token Verification Process (Partner App)
Extract Key ID:
- Decode JWT header to get
kid(Key ID) - Example:
"kid": "superapp-key-2025-01"
- Decode JWT header to get
Fetch Public Key:
- Request JWKS from:
https://supper-app.azurewebsites.net/.well-known/jwks.json - Find key matching the
kidfrom JWKS response
- Request JWKS from:
Verify Signature:
- Recreate:
base64Url(header) + "." + base64Url(payload) - Use RSA public key to verify the signature
- If signature matches → token is authentic and unmodified
- Recreate:
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 ❌ │
└─────────────────────────────────────────────────────────┘Security Properties
✅ 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
Comparison with Other Algorithms
| 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
Best Practices
- Always Verify Tokens: Never trust tokens without signature verification
- Cache JWKS: Fetch JWKS periodically and cache (with TTL) to reduce requests
- Handle Key Rotation: If
kidnot found, refresh JWKS and retry verification - Validate Claims: After signature verification, validate
exp,aud,issclaims - Use HTTPS: Always fetch JWKS over HTTPS to prevent MITM attacks
Example: JWKS Structure
{
"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
"SuperApp not available"
Cause: Partner App is not loaded within SuperApp
Solution: Ensure app is running in iframe or Capacitor WebView
"Request timeout"
Cause: SuperApp not responding or slow network
Solution: Increase timeout in configuration
const provider = new TokenProvider({ timeout: 10000 });"Token expired"
Cause: Token has expired
Solution: Get a new token by calling getToken() again
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();
}
}License
MIT
Support
- Email: [email protected]
Ready to use secure tokens? Install the package and get started! 🔐
