Skip to main content
This guide covers the complete lifecycle of sessions in Ory Kratos, from creation during login to revocation during logout.

Session creation

Sessions are created when a user successfully completes authentication.

Creation flow

1

Authentication completed

User completes a self-service flow (login, registration) or authenticates via Admin API.
2

Session activation

The session manager activates the session:
session/manager.go
func (m *Manager) ActivateSession(
    r *http.Request,
    session *Session,
    i *identity.Identity,
    authenticatedAt time.Time,
) error {
    session.Active = true
    session.AuthenticatedAt = authenticatedAt
    session.IssuedAt = authenticatedAt
    session.Identity = i
    session.IdentityID = i.ID
    session.SetAuthenticatorAssuranceLevel()
    session.SetSessionDeviceInformation(r)
    return nil
}
3

Session persisted

The session is stored in the database with a unique token.
4

Cookie/token issued

For browsers, a cookie is set. For API clients, the token is returned in the response.

Creating inactive sessions

For partial authentication (before completing MFA):
session/session.go
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,
    }
}

Session validation

Sessions are validated on every authenticated request.

Validation steps

1

Extract token

Token is extracted from cookie, Authorization header, or X-Session-Token header.
2

Database lookup

Session is retrieved from the database using the token.
3

Active check

Session must be active, not expired, and belong to an active identity:
func (s *Session) IsActive() bool {
    return s.Active && 
           s.ExpiresAt.After(time.Now()) && 
           (s.Identity == nil || s.Identity.IsActive())
}
4

AAL check (optional)

If an endpoint requires specific AAL, verify the session satisfies it.

Fetch from request

session/manager_http.go
func (m *Manager) FetchFromRequest(ctx context.Context, r *http.Request) (*Session, error) {
    // Try cookie first
    token, err := m.extractTokenFromCookie(r)
    if err != nil {
        // Try Authorization header
        token, err = m.extractTokenFromAuthHeader(r)
        if err != nil {
            // Try X-Session-Token header
            token, err = m.extractTokenFromCustomHeader(r)
            if err != nil {
                return nil, NewErrNoCredentialsForSession()
            }
        }
    }

    session, err := m.persister.GetSessionByToken(ctx, token)
    if err != nil {
        return nil, err
    }

    if !session.IsActive() {
        return nil, NewErrNoActiveSessionFound()
    }

    return session, nil
}

AAL validation

Check if a session satisfies required AAL:
func (m *Manager) DoesSessionSatisfy(
    ctx context.Context,
    sess *Session,
    matcher string,
    opts ...ManagerOptions,
) error {
    switch matcher {
    case "aal1":
        if sess.AuthenticatorAssuranceLevel >= AuthenticatorAssuranceLevel1 {
            return nil
        }
    case "aal2":
        if sess.AuthenticatorAssuranceLevel >= AuthenticatorAssuranceLevel2 {
            return nil
        }
    case "highest_available":
        availableAAL := sess.Identity.InternalAvailableAAL
        if sess.AuthenticatorAssuranceLevel >= availableAAL.ToAAL() {
            return nil
        }
    }
    return NewErrAALNotSatisfied(redirectURL)
}
The highest_available matcher requires users to authenticate with their highest available AAL. If a user has TOTP configured but only authenticated with password, they’ll be prompted for the second factor.

Session extension

Sessions can be extended to prevent expiration.

Automatic extension

Sessions are automatically extended on successful validation:
func (m *Manager) RefreshCookie(
    ctx context.Context,
    w http.ResponseWriter,
    r *http.Request,
    s *Session,
) error {
    if !s.CanBeRefreshed(ctx, m.r.Config()) {
        return nil
    }

    s.Refresh(ctx, m.r.Config())
    
    if err := m.r.SessionPersister().UpsertSession(ctx, s); err != nil {
        return err
    }

    return m.IssueCookie(ctx, w, r, s)
}

Manual extension via Admin API

Extend a session explicitly:
curl -X PATCH "https://kratos-admin/admin/sessions/{id}/extend"
Response:
HTTP/1.1 204 No Content

Refresh window

To reduce database writes, sessions are only refreshed when approaching expiration:
kratos.yml
session:
  lifespan: 720h  # 30 days
  earliest_possible_extend: 1h  # Only extend if < 1h left
Sessions are extended by resetting expires_at to now + lifespan. Setting earliest_possible_extend too low causes excessive database writes.

Session upgrades (MFA)

Sessions can be upgraded from AAL1 to AAL2.

MFA flow

1

Initial authentication

User authenticates with password (AAL1 session created).
2

MFA required

Application requires AAL2, user is redirected to complete second factor.
3

Second factor completed

User completes TOTP/WebAuthn challenge.
4

Session upgraded

Authentication method is added to session:
func (m *Manager) SessionAddAuthenticationMethods(
    ctx context.Context,
    sid uuid.UUID,
    methods ...AuthenticationMethod,
) error {
    session, err := m.r.SessionPersister().GetSession(ctx, sid)
    if err != nil {
        return err
    }

    for _, method := range methods {
        session.CompletedLoginForMethod(method)
    }

    session.SetAuthenticatorAssuranceLevel()
    session.AuthenticatedAt = time.Now().UTC()

    return m.r.SessionPersister().UpsertSession(ctx, session)
}

AAL errors

When AAL is insufficient:
{
  "error": {
    "id": "session_aal2_required",
    "code": 403,
    "status": "Forbidden",
    "reason": "Session does not fulfill the requested Authenticator Assurance Level",
    "details": {
      "redirect_browser_to": "https://example.com/login?aal=aal2&return_to=/protected"
    }
  }
}

Session revocation

Sessions can be revoked (logged out) in several ways.

Self-service logout

Users can revoke their own sessions:
curl -X DELETE "https://kratos-public/sessions" \
  --cookie "ory_kratos_session=..."
Users cannot revoke their current session via the DELETE /sessions/{id} endpoint. Use the logout flow instead.

Admin revocation

curl -X DELETE "https://kratos-admin/admin/sessions/{id}"

Revocation types

MethodEffectData retention
DeactivateSets active = falseSession data retained
DeleteRemoves from databaseSession data deleted

Bulk revocation

Revoke all sessions except current:
curl -X DELETE "https://kratos-public/sessions" \
  --cookie "ory_kratos_session=..."
Response:
{
  "count": 3
}

Session persistence

Database storage

Sessions are stored in the sessions table:
CREATE TABLE sessions (
    id UUID PRIMARY KEY,
    nid UUID NOT NULL,
    identity_id UUID NOT NULL,
    token VARCHAR(255) UNIQUE NOT NULL,
    logout_token VARCHAR(255) UNIQUE NOT NULL,
    active BOOLEAN NOT NULL DEFAULT TRUE,
    expires_at TIMESTAMP NOT NULL,
    authenticated_at TIMESTAMP NOT NULL,
    issued_at TIMESTAMP NOT NULL,
    aal VARCHAR(10) NOT NULL DEFAULT 'aal0',
    authentication_methods JSONB,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL
);

Session devices

Device information is stored separately:
CREATE TABLE session_devices (
    id UUID PRIMARY KEY,
    session_id UUID NOT NULL,
    identity_id UUID,
    nid UUID NOT NULL,
    ip_address VARCHAR(50),
    user_agent TEXT,
    location VARCHAR(255),
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL,
    FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
);

Session listing

List all sessions (Admin)

curl "https://kratos-admin/admin/sessions?page_size=100&active=true"
Query parameters:
  • page_size: Results per page
  • page_token: Pagination token
  • active: Filter by active status
  • expand: Include identity and/or devices

List identity sessions

curl "https://kratos-admin/admin/identities/{id}/sessions?active=true"

List my sessions (self-service)

curl "https://kratos-public/sessions" \
  --cookie "ory_kratos_session=..."
The self-service endpoint excludes the current session from results. Use /sessions/whoami to get the current session.

Session expandables

Control which associations are loaded:
curl "https://kratos-admin/admin/sessions/{id}?expand=identity&expand=devices"
Available expandables:
  • identity: Include full identity object
  • devices: Include device history

Performance considerations

Session optimization tips:
  • Use longer earliest_possible_extend to reduce database writes
  • Enable session caching for high-traffic read operations
  • Paginate session lists with keyset pagination
  • Regularly clean up expired sessions
  • Use database indexes on token, identity_id, and expires_at

Session cleanup

Kratos automatically removes expired sessions based on configuration. You can also manually trigger cleanup via housekeeping jobs.

Build docs developers (and LLMs) love