Skip to content

Sessions

When a user signs in through Seal, a session is created. The authentication response includes an access token (a short-lived JWT) and a refresh token (a long-lived opaque token). Together, these tokens represent the user’s active session and are used to verify that the session is still valid.

You can view and manage active sessions in the Seal portal by navigating to Users → select a user → Sessions tab. Each session shows the authentication method, status, last activity, and expiry time.


Your application is responsible for storing the tokens returned by Seal and using them to maintain the user’s session.

  • Store the access token in a secure, HTTP-only cookie in the user’s browser. This token is validated on each request to verify the user’s identity.
  • Store the refresh token on your backend — in a database, cache, or secure HTTP-only cookie. This token is used to obtain a new access token when the current one expires.

The typical request flow looks like this:

alt [Token valid] [Token expired] Request (with access token cookie) Validate access token (JWT) Response POST /auth/token (refresh_token grant) New access token + refresh token Update stored tokens Response (set new cookie) User Your Application Seal Auth

On each incoming request, your application validates the access token. If the token has expired, use the refresh token to obtain a new access token from Seal. If the refresh token is also expired or revoked, the user must sign in again.

The access token is a JWT signed with RS256 using your environment’s private key. You can validate it using any standard JWT library by fetching your environment’s public keys from the JWKS endpoint:

GET https://auth.seal.dev/jwk/YOUR_CLIENT_ID

The access token contains the following claims:

  • sub — the user’s ID (organization user ID)
  • sid — the session ID
  • type — always "access"
  • iss — the issuer
  • organization — the organization ID (present when the user belongs to an organization)
  • exp — expiration timestamp
  • iat — issued-at timestamp
import jwt # PyJWT
from jwt import PyJWKClient
jwks_client = PyJWKClient(
"https://auth.seal.dev/jwk/YOUR_CLIENT_ID",
cache_jwk_set=True,
lifespan=3600,
)
def verify_access_token(token: str) -> dict:
signing_key = jwks_client.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
)
return payload

The refresh token is an opaque string. Only a SHA-256 hash of the token is stored on Seal’s side — the plaintext is returned once during the token exchange and must be stored securely by your application.

Store the refresh token on your backend (in a database or secure HTTP-only cookie). Never expose it to client-side code.

To obtain a new access token, call the token endpoint with grant_type=refresh_token:

Terminal window
curl -X POST https://auth.seal.dev/auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "refresh_token=YOUR_REFRESH_TOKEN"

The response has the same shape as the initial token exchange — a new access token, the refresh token, and the user’s profile. See the API reference for the full response schema.

If a user belongs to multiple organizations, you can switch their active organization context by passing an organization_id to the refresh token grant. The new access token will contain the updated organization claim.

Terminal window
curl -X POST https://auth.seal.dev/auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "refresh_token=YOUR_REFRESH_TOKEN" \
-d "organization_id=org_target_org_id"

On success, the response includes a new access token with the organization claim set to the target organization. If the user does not have a membership in the specified organization, the request fails with an authentication error and the user must sign in again.

To sign a user out, follow these steps:

  1. Extract the session ID (sid claim) from the user’s access token
  2. Delete your application’s session (clear cookies, remove stored tokens)
  3. Redirect the user’s browser to Seal’s logout endpoint
  4. Seal revokes the session, clears authentication cookies, and redirects the user to your configured post-logout URL
import jwt
from fastapi import Request
from fastapi.responses import RedirectResponse
from urllib.parse import urlencode
@app.get("/logout")
async def logout(request: Request):
token = request.cookies.get("session")
if not token:
return RedirectResponse(url="/login")
# Extract the session ID from the access token
claims = jwt.decode(token, options={"verify_signature": False})
session_id = claims.get("sid")
# Build the Seal logout URL
params = urlencode({
"sessionId": session_id,
"redirectTo": "https://yourapp.com/login",
})
logout_url = f"https://api.seal.dev/auth/logout?{params}"
# Clear your app's session cookie and redirect to Seal
response = RedirectResponse(url=logout_url)
response.delete_cookie(key="session")
return response

Session settings are configured per environment in the Seal portal under Settings → Session Policy.

The following options are available:

  • Maximum session length — the absolute maximum duration of a session, regardless of activity. After this period, the session expires and the user must sign in again. Default: 30 days. Range: 1 hour to 90 days.
  • Access token duration — how long each access token JWT is valid before it expires and must be refreshed. Keep this short to limit the window of exposure if a token is compromised. Default: 5 minutes. Range: 1 minute to 1 hour.
  • Inactivity timeout — if enabled, the session expires when no refresh occurs within this window. Each time a refresh token is used, the inactivity clock resets. Default: 1 hour (disabled by default). Range: 5 minutes to 24 hours.

When a user signs out, Seal redirects them to a registered logout redirect URI. You can configure these in the portal under Settings → Logout Redirect URIs.

The first URI you register becomes the default. If the redirectTo parameter on the logout endpoint is omitted or does not match a registered URI, Seal uses the default logout redirect.

You can override the default redirect on a per-request basis by passing a redirectTo query parameter to the logout endpoint. The value must exactly match a registered logout redirect URI.