Skip to main content

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

{
"alg": "RS256",
"typ": "JWT",
"kid": "gw-2025-01"
}
FieldDescription
algSigning algorithm (always RS256)
typToken type (always JWT)
kidKey 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)

ClaimTypeDescription
issstringIssuer - always "generalwisdom.com"
substringSubject - the user ID
iatnumberIssued At - Unix timestamp when the JWT was created
expnumberExpiration Time - Unix timestamp when the session ends

Custom Claims

ClaimTypeDescription
sessionIdstringUnique session identifier (used for purchases, extensions, and analytics)
applicationIdstringYour registered application ID (verify this matches your app)
userIdstringGeneral Wisdom user ID
orgIdstringOrganization ID the user belongs to
emailstringUser's email address
startTimenumberSession start time (Unix timestamp)
durationMinutesnumberPurchased 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:

CheckPurposeFailure Response
Signature (RS256 + JWKS)Proves token was issued by General Wisdom401 Unauthorized
Expiration (exp claim)Ensures session is still valid401 Unauthorized
Issuer (iss claim)Confirms token source is GW401 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

ErrorCauseResolution
Invalid signatureToken was tampered with or wrong JWKS endpointVerify jwksUri configuration
Token expiredSession time has elapsedRedirect user back to marketplace
Invalid issuerToken not from General WisdomEnsure you are checking the correct issuer
Application ID mismatchToken purchased for a different appVerify APPLICATION_ID in your configuration
JWKS fetch failedCannot reach the JWKS endpointCheck 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: