Sessions represent authenticated user contexts in Ory Kratos. After successful authentication, Kratos creates a session that tracks the user’s identity and authentication factors.
Session structure
The Session struct is defined in session/session.go:72:
type Session struct {
// Unique session identifier
ID uuid.UUID `json:"id"`
// Active state
Active bool `json:"active"`
// Expiration timestamp
ExpiresAt time.Time `json:"expires_at"`
// When the user authenticated
AuthenticatedAt time.Time `json:"authenticated_at"`
// Authenticator Assurance Level (aal1, aal2)
AuthenticatorAssuranceLevel identity.AuthenticatorAssuranceLevel `json:"authenticator_assurance_level"`
// Authentication Methods References
AMR AuthenticationMethods `json:"authentication_methods"`
// When the session was issued
IssuedAt time.Time `json:"issued_at"`
// Logout token for revoking the session
LogoutToken string `json:"-"`
// The authenticated identity
Identity *identity.Identity `json:"identity"`
// Device history
Devices []Device `json:"devices"`
// Session token (not exposed in JSON)
Token string `json:"-"`
}
Session tokens
Sessions are identified by session tokens, which come in different formats depending on the flow type.
Browser sessions
For browser flows, the session token is stored in an HTTP-only cookie:
Set-Cookie: ory_kratos_session=ory_st_<token>; Path=/; HttpOnly; Secure; SameSite=Lax
Cookie attributes:
- HttpOnly - JavaScript cannot access the cookie
- Secure - Only sent over HTTPS
- SameSite=Lax - CSRF protection
- Path=/ - Available across the domain
API sessions
For API flows, the session token is returned in the response body:
{
"session": {
"id": "...",
"active": true,
"identity": {...}
},
"session_token": "ory_st_..."
}
Clients must include the token in the Authorization header or X-Session-Token header:
Authorization: Bearer ory_st_...
or
X-Session-Token: ory_st_...
Token generation
Session tokens are generated during session creation (session/session.go:242):
func NewInactiveSession() *Session {
return &Session{
ID: uuid.Nil,
Token: x.OrySessionToken + randx.MustString(32, randx.AlphaNum),
LogoutToken: x.OryLogoutToken + randx.MustString(32, randx.AlphaNum),
Active: false,
AuthenticatorAssuranceLevel: identity.NoAuthenticatorAssuranceLevel,
}
}
Tokens are prefixed with ory_st_ for session tokens and ory_lt_ for logout tokens.
Session tokens are sensitive credentials. Treat them like passwords - never log or expose them unnecessarily.
Session lifecycle
1. Creation
Sessions are created after successful authentication through any flow (login, registration, recovery).
Creation steps:
- User successfully authenticates
- Kratos creates a new session with:
- Unique ID
- Session token
- Logout token
- Reference to identity
- Authentication methods used
- Expiration time
- Session is persisted to the database
- Token is returned to client (cookie or API response)
2. Validation
Sessions are validated on each request that requires authentication.
Validation checks:
- Token exists and matches a session in the database
- Session is active
- Session has not expired
- Associated identity is active
From session/session.go:282:
func (s *Session) IsActive() bool {
return s.Active &&
s.ExpiresAt.After(time.Now()) &&
(s.Identity == nil || s.Identity.IsActive())
}
3. Refresh
Sessions can be refreshed to extend their lifetime (session/session.go:286):
func (s *Session) Refresh(ctx context.Context, c lifespanProvider) *Session {
s.ExpiresAt = time.Now().Add(c.SessionLifespan(ctx)).UTC()
return s
}
Refresh conditions:
Sessions can be refreshed when they are within the refresh window (session/session.go:298):
func (s *Session) CanBeRefreshed(ctx context.Context, c refreshWindowProvider) bool {
return s.ExpiresAt.Add(-c.SessionRefreshMinTimeLeft(ctx)).Before(time.Now())
}
Configured via:
session:
lifespan: 24h
earliest_possible_extend: 1h
4. Revocation
Sessions can be revoked (logged out) in several ways:
Explicit logout:
GET /self-service/logout/browser?token=<logout_token>
Admin API revocation:
DELETE /admin/identities/<id>/sessions/<session_id>
Bulk revocation:
Revoke all sessions for an identity:
DELETE /admin/identities/<id>/sessions
Whoami endpoint
The /sessions/whoami endpoint validates sessions and returns the current user’s information.
Request
Browser:
GET /sessions/whoami
Cookie: ory_kratos_session=...
API:
GET /sessions/whoami
Authorization: Bearer ory_st_...
Response
{
"id": "session-id",
"active": true,
"expires_at": "2024-01-01T12:00:00Z",
"authenticated_at": "2024-01-01T08:00:00Z",
"authenticator_assurance_level": "aal1",
"authentication_methods": [
{
"method": "password",
"aal": "aal1",
"completed_at": "2024-01-01T08:00:00Z"
}
],
"issued_at": "2024-01-01T08:00:00Z",
"identity": {
"id": "identity-id",
"schema_id": "default",
"schema_url": "https://example.com/schemas/default",
"state": "active",
"traits": {
"email": "[email protected]",
"name": "Jane Doe"
},
"verifiable_addresses": [...],
"metadata_public": {...}
},
"devices": [
{
"id": "device-id",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"location": "San Francisco, US"
}
]
}
Error responses
No session:
{
"error": {
"id": "session_inactive",
"code": 401,
"status": "Unauthorized",
"reason": "No active session was found in this request.",
"message": "request does not have a valid authentication session"
}
}
AAL not satisfied:
{
"error": {
"id": "session_aal2_required",
"code": 403,
"status": "Forbidden",
"message": "authentication assurance level aal2 is required"
},
"redirect_browser_to": "/self-service/login?aal=aal2"
}
Authenticator assurance levels (AAL)
AAL indicates the strength of authentication used to create the session.
AAL values
aal0 - No authentication
aal1 - Single-factor authentication (password, OIDC, etc.)
aal2 - Multi-factor authentication (password + TOTP/WebAuthn)
AAL enforcement
Request AAL2 when checking session:
GET /sessions/whoami?aal=aal2
If the session doesn’t satisfy AAL2, Kratos returns a redirect to upgrade the session.
Authentication Methods Reference (AMR)
AMR tracks which authentication methods were used during login.
AMR structure
Defined in session/session.go:314:
type AuthenticationMethod struct {
Method identity.CredentialsType `json:"method"`
AAL identity.AuthenticatorAssuranceLevel `json:"aal"`
CompletedAt time.Time `json:"completed_at"`
Provider string `json:"provider,omitempty"`
Organization string `json:"organization,omitempty"`
}
Example AMR
Single-factor login:
[
{
"method": "password",
"aal": "aal1",
"completed_at": "2024-01-01T08:00:00Z"
}
]
Multi-factor login:
[
{
"method": "password",
"aal": "aal1",
"completed_at": "2024-01-01T08:00:00Z"
},
{
"method": "totp",
"aal": "aal2",
"completed_at": "2024-01-01T08:00:15Z"
}
]
OIDC login:
[
{
"method": "oidc",
"aal": "aal1",
"completed_at": "2024-01-01T08:00:00Z",
"provider": "google"
}
]
AAL calculation
The session’s AAL is calculated from AMR entries (session/session.go:193):
func (s *Session) SetAuthenticatorAssuranceLevel() {
var isAAL1, isAAL2 bool
for _, amr := range s.AMR {
switch amr.AAL {
case identity.AuthenticatorAssuranceLevel1:
isAAL1 = true
case identity.AuthenticatorAssuranceLevel2:
isAAL2 = true
}
}
if isAAL1 && isAAL2 {
s.AuthenticatorAssuranceLevel = identity.AuthenticatorAssuranceLevel2
} else if isAAL1 {
s.AuthenticatorAssuranceLevel = identity.AuthenticatorAssuranceLevel1
}
}
Device tracking
Sessions track device information for security and audit purposes.
Device structure
From session/session.go:39:
type Device struct {
ID uuid.UUID `json:"id"`
IPAddress *string `json:"ip_address"`
UserAgent *string `json:"user_agent"`
Location *string `json:"location"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}
Device capture
Device information is captured from the HTTP request (session/session.go:252):
func (s *Session) SetSessionDeviceInformation(r *http.Request) {
device := Device{
SessionID: s.ID,
IPAddress: new(httpx.ClientIP(r)),
}
agent := r.Header["User-Agent"]
if len(agent) > 0 {
device.UserAgent = new(strings.Join(agent, " "))
}
// Extract geo location from Cloudflare headers
var clientGeoLocation []string
if r.Header.Get("Cf-Ipcity") != "" {
clientGeoLocation = append(clientGeoLocation, r.Header.Get("Cf-Ipcity"))
}
if r.Header.Get("Cf-Ipcountry") != "" {
clientGeoLocation = append(clientGeoLocation, r.Header.Get("Cf-Ipcountry"))
}
loc := strings.Join(clientGeoLocation, ", ")
device.Location = &loc
s.Devices = append(s.Devices, device)
}
Device tracking helps users identify suspicious sessions and provides context for security monitoring.
Session storage
Sessions are stored in the database with the following characteristics:
- Table:
sessions
- Indexed by: Token (for fast lookup)
- Filtered by: NID (for multi-tenancy)
- Includes: Identity relationship (eager loaded)
Session tokens in database
Session tokens are stored in plaintext in the database. The token serves as the lookup key.
Protect database access - session tokens in the database are equivalent to passwords.
Session configuration
session:
# Session lifetime
lifespan: 24h
# Cookie settings for browser sessions
cookie:
domain: .example.com
path: /
same_site: Lax
persistent: false
# Earliest time before expiry when session can be refreshed
earliest_possible_extend: 1h
# Whoami endpoint settings
whoami:
tokenizer:
templates:
# JWT template for session tokenization
jwt:
enabled: true
claims:
issuer: https://example.com
audience:
- https://api.example.com
Best practices
Session security
- Always use HTTPS in production
- Set appropriate cookie domains and paths
- Implement session timeout policies
- Monitor for suspicious device patterns
Session validation
- Validate sessions on every protected request
- Check AAL requirements for sensitive operations
- Handle session expiration gracefully
- Refresh sessions within the refresh window
Token handling
- Never expose session tokens in logs
- Store API tokens securely (encrypted storage on device)
- Implement token rotation for long-lived sessions
- Revoke sessions on password change
Disabled identities can still use existing sessions until they expire. Revoke sessions when disabling identities.