Skip to main content
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:
  1. User successfully authenticates
  2. Kratos creates a new session with:
    • Unique ID
    • Session token
    • Logout token
    • Reference to identity
    • Authentication methods used
    • Expiration time
  3. Session is persisted to the database
  4. 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.

Build docs developers (and LLMs) love