Backend validation
The SDK does not create user sessions. Your backend must validate the sessionToken with TryMellon and then create your own session (e.g. set a cookie or return a JWT).
Validate the token
Call the TryMellon API with the token your frontend received from register() or authenticate():
GET https://api.trymellonauth.com/v1/sessions/validate
Authorization: Bearer {sessionToken}
Or with a different base URL if you configured one:
GET {apiBaseUrl}/v1/sessions/validate
Authorization: Bearer {sessionToken}
Response
Success (200):
{
"valid": true,
"user_id": "usr_xxx",
"external_user_id": "user_123",
"tenant_id": "tenant_xxx",
"app_id": "app_xxx"
}
Use external_user_id to identify the user in your system. Then create your own session (e.g. set an httpOnly cookie or issue a JWT) and redirect or return the appropriate response.
Invalid or expired (401):
Do not create a session; return an error to the client.
When to validate
| Approach | Latency | Security | Offline |
|---|---|---|---|
Always call /v1/sessions/validate | +50ms | Highest (real-time revocation) | No |
Validate JWT locally (check exp, sig) | 0ms | High (no revocation check) | Yes |
| Cache validation for 5 min | 0ms (hot) | Medium | Partial |
Recommendation: Call /v1/sessions/validate on the login callback
(this is your trust boundary). For subsequent API requests, trust the
session token locally by checking its expiration.
Login callback (always validate)
This is where your user “logs in.” Always call TryMellon here:
app.post('/api/auth/callback', async (req, res) => {
const { session_token } = req.body;
const validation = await validateWithTryMellon(session_token);
if (!validation.valid) return res.status(401).json({ error: 'Invalid session' });
// Create YOUR session (cookie, JWT, etc.)
req.session.userId = validation.external_user_id;
res.json({ ok: true });
});
Subsequent requests (trust locally)
After login, your app has its own session. You don’t need to call TryMellon again unless you need real-time revocation checks.
Example (Node/Express-style)
// POST /api/login
const sessionToken = req.body.sessionToken
if (!sessionToken) {
return res.status(400).json({ error: 'sessionToken required' })
}
const response = await fetch('https://api.trymellonauth.com/v1/sessions/validate', {
method: 'GET',
headers: { Authorization: `Bearer ${sessionToken}` },
})
if (!response.ok) {
return res.status(401).json({ error: 'Invalid session' })
}
const data = await response.json()
// data.valid, data.external_user_id, data.tenant_id, data.app_id
// Create your own session (e.g. set cookie, store in DB)
// Then redirect or return success
After validation, the session token is consumed by your backend; the frontend should not store it. Use your own session cookie or token for subsequent requests.
On this site: The frontend calls setSessionOnServer(sessionToken), which POSTs the token to /api/auth/set-session. That endpoint validates the token with TryMellon (GET /v1/sessions/validate); only if the response is 200 does it set an httpOnly cookie. In development only, the sandbox token (SANDBOX_SESSION_TOKEN) is accepted without calling the API. See the auth flow in the repo for the exact pattern.
Env (este sitio): La URL del backend en este sitio se configura con la variable de entorno PUBLIC_TRYMELLON_API_BASE_URL. Ver README del repositorio (Variables de entorno) para valores en producción y desarrollo.
Development / sandbox: accepting the sandbox token
When the frontend uses the SDK with sandbox mode (sandbox: true), it receives a fixed session token (the constant SANDBOX_SESSION_TOKEN from @trymellon/js). In development only, your backend may accept this token and create a session for a test user without calling the TryMellon API.
Security: You MUST NOT accept the sandbox token in production. Only allow it when NODE_ENV === 'development' (or an equivalent flag). In production, always validate every session token with TryMellon.
Example (Node/Express-style):
import { SANDBOX_SESSION_TOKEN } from '@trymellon/js'
// POST /api/login
const sessionToken = req.body.sessionToken
if (!sessionToken) {
return res.status(400).json({ error: 'sessionToken required' })
}
// In development only: accept sandbox token without calling TryMellon
if (process.env.NODE_ENV === 'development' && sessionToken === SANDBOX_SESSION_TOKEN) {
// Create session for a test user (e.g. external_user_id: 'sandbox')
// Then redirect or return success
return createTestSessionAndRespond(req, res)
}
const response = await fetch('https://api.trymellonauth.com/v1/sessions/validate', {
method: 'GET',
headers: { Authorization: `Bearer ${sessionToken}` },
})
if (!response.ok) {
return res.status(401).json({ error: 'Invalid session' })
}
const data = await response.json()
// Create your own session (e.g. set cookie), then redirect or return success
Validate in your language
The example above uses Node/Express. Below are equivalent snippets for other backends.
Node.js (Express)
See the Example (Node/Express-style) above.
Python (requests)
import requests
TRYMELLON_API = "https://api.trymellonauth.com"
def validate_session(session_token: str) -> dict:
"""Validate a TryMellon session token. Call this on your login callback."""
response = requests.get(
f"{TRYMELLON_API}/v1/sessions/validate",
headers={"Authorization": f"Bearer {session_token}"},
timeout=5,
)
if response.status_code != 200:
raise ValueError(f"Invalid session: {response.status_code}")
data = response.json()
if not data.get("valid"):
raise ValueError("Session not valid")
return data # { valid, user_id, external_user_id, tenant_id, app_id }
Go
package auth
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
type SessionValidation struct {
Valid bool `json:"valid"`
UserID string `json:"user_id"`
ExternalUserID string `json:"external_user_id"`
TenantID string `json:"tenant_id"`
AppID string `json:"app_id"`
}
func ValidateSession(sessionToken string) (*SessionValidation, error) {
client := &http.Client{Timeout: 5 * time.Second}
req, err := http.NewRequest("GET",
"https://api.trymellonauth.com/v1/sessions/validate", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+sessionToken)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("invalid session: status %d", resp.StatusCode)
}
var result SessionValidation
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if !result.Valid {
return nil, fmt.Errorf("session not valid")
}
return &result, nil
}
Java (HttpClient)
import java.net.URI;
import java.net.http.*;
import com.google.gson.Gson;
public class TryMellonValidator {
private static final String API = "https://api.trymellonauth.com";
private static final HttpClient client = HttpClient.newHttpClient();
private static final Gson gson = new Gson();
public record SessionResult(
boolean valid, String user_id, String external_user_id,
String tenant_id, String app_id
) {}
public static SessionResult validate(String sessionToken) throws Exception {
var request = HttpRequest.newBuilder()
.uri(URI.create(API + "/v1/sessions/validate"))
.header("Authorization", "Bearer " + sessionToken)
.timeout(java.time.Duration.ofSeconds(5))
.GET()
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200)
throw new RuntimeException("Invalid session: " + response.statusCode());
var result = gson.fromJson(response.body(), SessionResult.class);
if (!result.valid())
throw new RuntimeException("Session not valid");
return result;
}
}
Ruby (net/http)
require 'net/http'
require 'json'
require 'uri'
TRYMELLON_API = "https://api.trymellonauth.com"
def validate_session(session_token)
uri = URI("#{TRYMELLON_API}/v1/sessions/validate")
req = Net::HTTP::Get.new(uri)
req['Authorization'] = "Bearer #{session_token}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true,
open_timeout: 5, read_timeout: 5) { |http|
http.request(req)
}
raise "Invalid session: #{res.code}" unless res.code == '200'
data = JSON.parse(res.body)
raise "Session not valid" unless data['valid']
data # { "valid", "user_id", "external_user_id", "tenant_id", "app_id" }
end