Session Creation
When a user purchases a session on your application through the General Wisdom marketplace, the platform generates a signed JWT and redirects the user to your application's launch URL. This page covers the full creation flow, JWT structure, and how to extract and validate the session token.
How Sessions Are Created
sequenceDiagram
participant User
participant Marketplace as GW Marketplace
participant Platform as GW Platform API
participant App as Your Application
User->>Marketplace: Browse & select your app
User->>Marketplace: Purchase session (SMART tokens)
Marketplace->>Platform: Create session request
Platform->>Platform: Deduct tokens from balance
Platform->>Platform: Generate signed JWT (RS256)
Platform->>Marketplace: Return session JWT
Marketplace->>App: Redirect to launch URL with ?gwSession=JWT
App->>App: Extract JWT from URL
App->>Platform: Fetch JWKS public keys
App->>App: Validate signature, claims, expiration
App->>User: Grant access to application
Launch URL Format
When General Wisdom redirects users to your application, the URL follows this pattern:
https://your-app.com?gwSession=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imd3LTIwMjUtMDEifQ...
The gwSession query parameter contains the complete JWT. Your application must extract this parameter on page load to begin the session.
JWT Structure
Header
{
"alg": "RS256",
"typ": "JWT",
"kid": "gw-2025-01"
}
| Field | Description |
|---|---|
alg | Signing algorithm (always RS256) |
typ | Token type (always JWT) |
kid | Key ID used to look up the correct public key from JWKS |
Payload (Claims)
{
"sessionId": "35iiYDoY1fSwSpYX22H8GP7x61o",
"applicationId": "your-app-id",
"userId": "a47884c8-50d1-7040-2de8-b7801699643c",
"orgId": "org-789",
"email": "user@example.com",
"startTime": 1763599337,
"durationMinutes": 60,
"iss": "generalwisdom.com",
"sub": "a47884c8-50d1-7040-2de8-b7801699643c",
"exp": 1763602937,
"iat": 1763599337
}
Standard Claims (RFC 7519)
| Claim | Type | Description |
|---|---|---|
iss | string | Issuer - always "generalwisdom.com" |
sub | string | Subject - the user ID |
iat | number | Issued At - Unix timestamp when the JWT was created |
exp | number | Expiration Time - Unix timestamp when the session ends |
Custom Claims
| Claim | Type | Description |
|---|---|---|
sessionId | string | Unique session identifier (used for purchases, extensions, and analytics) |
applicationId | string | Your registered application ID (verify this matches your app) |
userId | string | General Wisdom user ID |
orgId | string | Organization ID the user belongs to |
email | string | User's email address |
startTime | number | Session start time (Unix timestamp) |
durationMinutes | number | Purchased session duration in minutes |
Extracting the JWT
Client-Side (Browser)
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('gwSession');
if (token) {
// Send to your server for validation
await validateAndStartSession(token);
}
Server-Side (Express)
app.get('/launch', (req, res) => {
const token = req.query.gwSession as string;
if (!token) {
return res.status(400).json({ error: 'Missing gwSession parameter' });
}
// Validate token (see below)
});
Using the SDK
The SDK handles extraction automatically when you call initialize():
import MarketplaceSDK from '@mission_sciences/provider-sdk';
const sdk = new MarketplaceSDK({
applicationId: 'your-app-id',
apiKey: 'gwsk_...',
environment: 'production',
hooks: {
onSessionStart: async (context) => {
// Session is validated and active
console.log('Session started for:', context.userId);
},
},
});
await sdk.initialize();
// SDK automatically extracts ?gwSession from URL and validates it
Validating the JWT
Server-Side Validation (Required)
Always validate the JWT signature on your server. Client-side decoding is insufficient for security.
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({
jwksUri: 'https://api.generalwisdom.com/.well-known/jwks.json',
cache: true,
cacheMaxAge: 3600000, // Cache keys for 1 hour
});
function getSigningKey(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) {
client.getSigningKey(header.kid, (err, key) => {
if (err) return callback(err);
const signingKey = key?.getPublicKey();
callback(null, signingKey);
});
}
function validateSessionJWT(token: string): Promise<SessionClaims> {
return new Promise((resolve, reject) => {
jwt.verify(
token,
getSigningKey,
{
issuer: 'generalwisdom.com',
algorithms: ['RS256'],
},
(err, decoded) => {
if (err) return reject(err);
resolve(decoded as SessionClaims);
}
);
});
}
Validation Checks
Your server must verify:
| Check | Purpose | Failure Response |
|---|---|---|
| Signature (RS256 + JWKS) | Proves token was issued by General Wisdom | 401 Unauthorized |
Expiration (exp claim) | Ensures session is still valid | 401 Unauthorized |
Issuer (iss claim) | Confirms token source is GW | 401 Unauthorized |
Application ID (applicationId) | Restricts to JWTs for your app (optional) | 403 Forbidden |
Express Middleware Example
async function validateSession(req, res, next) {
const token = req.query.gwSession || req.headers['x-gw-session'];
if (!token) {
return res.status(401).json({ error: 'No session token provided' });
}
try {
const claims = await validateSessionJWT(token);
// Optional: verify application ID
if (claims.applicationId !== process.env.APPLICATION_ID) {
return res.status(403).json({ error: 'Token for different application' });
}
// Attach session data to request
req.gwSession = {
sessionId: claims.sessionId,
userId: claims.userId,
orgId: claims.orgId,
email: claims.email,
expiresAt: new Date(claims.exp * 1000),
durationMinutes: claims.durationMinutes,
timeRemaining: () => claims.exp - Math.floor(Date.now() / 1000),
};
next();
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Session expired',
message: 'Please purchase a new session from the marketplace.',
});
}
return res.status(401).json({ error: 'Invalid token' });
}
}
Error Handling
| Error | Cause | Resolution |
|---|---|---|
Invalid signature | Token was tampered with or wrong JWKS endpoint | Verify jwksUri configuration |
Token expired | Session time has elapsed | Redirect user back to marketplace |
Invalid issuer | Token not from General Wisdom | Ensure you are checking the correct issuer |
Application ID mismatch | Token purchased for a different app | Verify APPLICATION_ID in your configuration |
JWKS fetch failed | Cannot reach the JWKS endpoint | Check network connectivity and firewall rules |
Security Best Practices
- Always validate on the server - Client-side JWT decoding is for display only.
- Cache JWKS keys - Set a 1-hour TTL to avoid repeated network calls.
- Use HTTPS - Never accept sessions over unencrypted connections.
- Never log JWT tokens - Tokens contain sensitive session data.
- Never store JWTs in localStorage - Use sessionStorage or memory to avoid XSS exposure.
- Handle clock skew - Allow up to 60 seconds of clock drift when checking expiration.
Next Steps
Once your application has validated the session JWT and granted access, you can:
- Offer in-session purchases to let users buy add-ons
- Extend the session when users need more time
- Handle session termination gracefully
- Listen for webhook events on your backend