A Lambda execution environment can be reused, but an application must not depend on its memory or temporary files surviving for another invocation. Keep durable user session state in an external store, or use a verifiable token when server-side session state is unnecessary. AWS recommends designing standard Lambda functions to be stateless and committing permanent changes to durable services before the function exits; see AWS guidance on Lambda application design.

First separate two things that are often both called a “session”:

  • An authentication token carries signed identity and authorization claims. The API can validate it without loading a per-user session record on every request.
  • An opaque session ID is a random secret that the server looks up. A server-side record lets the application change session data and provides a control point for rejecting later requests before the browser cookie expires. The enforcement delay depends on the store consistency, authorizer cache, and any request already in progress.

For a typical API that only needs verified identity and scopes, use an identity provider such as Amazon Cognito with an API Gateway authorizer. Choose a server-side session when you need server-controlled revocation, mutable session data, or a browser login that should hide identity-provider tokens from JavaScript. API Gateway routes and authorizers can enforce access; API Gateway is not a durable session store.

AWS Lambda and Statelessness

AWS Lambda

What stateless means in practice

Lambda can reuse an execution environment to reduce initialization work. Module-level SDK clients and other reusable connections can therefore improve efficiency. Reuse is an optimization, not a persistence guarantee: a later request can run in a different environment, and an environment can be retired. Do not keep a user's login, cart, or authorization decision in a global variable or local file and expect the next invocation to find it.

Store data that must survive an invocation in a database or object store. For application state patterns such as idempotent writes, concurrency control, and event processing, see 5 Patterns for Resilient Serverless State Management on AWS.

What a session needs

A session design defines how a request proves identity, how long that proof is valid, how the application finds any server-side data, and how sign-out or account changes take effect. Lambda changes where state must live; it does not change these application responsibilities.

Session Management Methods for Lambda

Use Cognito or another identity provider when a token is enough

Amazon Cognito user pools issue ID, access, and refresh tokens for different purposes. Use an access token for API authorization; an ID token describes the authenticated user and is not a substitute for an access token. For an HTTP API, API Gateway's JWT authorizer can verify a configured issuer, audience, token signature, expiry, and route scopes, then pass validated claims to the Lambda integration. REST APIs have a separate Cognito user-pool authorizer option.

A self-contained JWT is convenient because a request can be checked without a database lookup, but an API that only checks its signature and expiry does not consult a live session record. Cognito token revocation prevents use with Cognito APIs, but AWS notes that a revoked token can still pass local JWT signature-and-expiry verification until it expires. If a security or account event must take effect immediately at your API, use a server-side revocation check or opaque session. If you can tolerate a delay, keep access-token lifetimes short enough for the window your product accepts. See Cognito's token-revocation guidance.

Use a server-side record when sessions must be revocable

Give the client a high-entropy, opaque session ID and store only a one-way hash of that ID with the minimum session data the application needs. On each protected request, hash the presented ID, retrieve the record by its primary key, and check its expiry in application code.

OptionGood fitTrade-off
Amazon DynamoDBDurable, shared session records with key-based reads and updates.Adds a service call to the request. TTL cleanup is asynchronous, so the application must enforce expiry.
Amazon ElastiCache for Valkey or Redis OSSLow-latency lookups where an in-memory store and its operating model fit.Choose capacity, eviction, failover, and durability settings deliberately; it is not a durable database by default.
Amazon S3Session-related files or larger objects referenced by an application record.Usually a poor primary store for a small record read on each authenticated request.

For most applications that need revocation without introducing a separate cache, a small DynamoDB item keyed by a hashed session ID is a straightforward starting point. Keep cart, profile, or workflow data in the data model that owns it instead of turning the authentication session into a general-purpose data container.

API Gateway validates or routes requests; it does not store sessions

API Gateway can call a JWT authorizer or a Lambda authorizer before invoking a route. Put custom session lookup logic in a Lambda authorizer when it should protect many routes, or in the integration Lambda when authorization is specific to that operation. In either design, the session record remains in a data store.

For an HTTP API Lambda proxy integration using payload format 2.0, API Gateway places incoming cookies in the event's cookies array. A Lambda response can set browser cookies with a cookies array, which API Gateway returns as Set-Cookie headers. This is HTTP transport, not session storage; see AWS's documentation for HTTP API Lambda proxy payloads.

Authorizer caching changes revocation behavior. While an authorization result is cached, API Gateway can reuse an earlier Allow without calling the authorizer or checking DynamoDB again. To avoid cache-based delay after revocation, disable that cache and use a lookup with suitable consistency; for DynamoDB, the example uses a strongly consistent base-table read in the same Region. Otherwise set the cache lifetime within the revocation delay the application accepts, and configure identity sources so the cache key identifies the relevant caller. A request already authorized may still finish. Read the API type's Lambda authorizer caching rules before enabling it.

Setting Up Session Management in Lambda

The example below uses a DynamoDB table for opaque browser sessions. Create a table with a string partition key named sessionHash and on-demand billing:

aws dynamodb create-table \
    --table-name UserSessions \
    --attribute-definitions AttributeName=sessionHash,AttributeType=S \
    --key-schema AttributeName=sessionHash,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST
aws dynamodb wait table-exists --table-name UserSessions

After the table becomes active, enable TTL on a numeric Unix epoch-seconds attribute named expiresAt:

aws dynamodb update-time-to-live \
    --table-name UserSessions \
    --time-to-live-specification '{"Enabled":true,"AttributeName":"expiresAt"}'

The function's execution role should have only the required item operations on this table, such as dynamodb:PutItem, dynamodb:GetItem, and dynamodb:DeleteItem. Add dynamodb:UpdateItem only if the function renews or changes sessions. Do not grant broad DynamoDB access or accept the user ID for a new session directly from an untrusted request; pass the identity established by your login flow.

Create, look up, and revoke an opaque session

This Python example uses a cryptographically secure random token, stores its SHA-256 hash, and checks the precise expiry on every lookup. The table and SDK resource are initialized outside the handler for reuse, but session state is read from DynamoDB. Handle store errors at the API boundary and fail closed if the session cannot be checked.

import hashlib
import os
import secrets
import time

import boto3
from http.cookies import CookieError, SimpleCookie

SESSION_LIFETIME_SECONDS = 3600
SESSION_COOKIE_NAME = "__Host-sid"

table = boto3.resource("dynamodb").Table(os.environ["SESSIONS_TABLE"])


def session_key(token):
    return hashlib.sha256(token.encode("utf-8")).hexdigest()


def issue_session(authenticated_user_id):
    token = secrets.token_urlsafe(32)
    now = int(time.time())
    expires_at = now + SESSION_LIFETIME_SECONDS

    table.put_item(
        Item={
            "sessionHash": session_key(token),
            "userId": authenticated_user_id,
            "createdAt": now,
            "expiresAt": expires_at,
            "version": 1,
        },
        ConditionExpression="attribute_not_exists(sessionHash)",
    )
    return token, expires_at


def session_from_event(event):
    cookie_values = event.get("cookies", [])
    if not isinstance(cookie_values, list) or any(not isinstance(value, str) for value in cookie_values):
        return None

    cookie = SimpleCookie()
    try:
        cookie.load("; ".join(cookie_values))
    except CookieError:
        return None
    morsel = cookie.get(SESSION_COOKIE_NAME)
    token = morsel.value if morsel else None

    if not isinstance(token, str) or not token or len(token) > 256:
        return None

    response = table.get_item(
        Key={"sessionHash": session_key(token)},
        ConsistentRead=True,
    )
    session = response.get("Item")
    now = int(time.time())

    if not session:
        return None
    try:
        expires_at = int(session.get("expiresAt", 0))
    except (TypeError, ValueError, OverflowError):
        return None
    if expires_at <= now:
        return None
    return session


def revoke_session(token):
    if isinstance(token, str) and token and len(token) <= 256:
        table.delete_item(Key={"sessionHash": session_key(token)})


def login_response(authenticated_user_id):
    token, _ = issue_session(authenticated_user_id)
    return {
        "statusCode": 204,
        "headers": {"cache-control": "no-store"},
        "cookies": [
            SESSION_COOKIE_NAME + "=" + token
            + "; Path=/; Max-Age=" + str(SESSION_LIFETIME_SECONDS)
            + "; Secure; HttpOnly; SameSite=Lax"
        ],
        "body": "",
    }

The login handler should call login_response only after credentials or an identity-provider callback have been verified. This response example assumes an HTTP API Lambda proxy integration with payload format 2.0. Do not return the session token in a page, URL, log entry, or JSON body. On sign-out, delete the corresponding DynamoDB item and return a clearing cookie with the same name and path, for example __Host-sid=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=Lax.

Connect the API and validate each request

  1. Configure API Gateway to invoke the Lambda integration, and choose an HTTP API JWT authorizer or a Lambda authorizer if its checks match the authentication design.
  2. For cookie sessions, have the integration or custom authorizer read the cookie and perform the server-side lookup. If a Lambda authorizer cache is enabled, account for its revocation delay as described above.
  3. For every protected operation, use the verified userId from the session or authorizer context. Check that the user is allowed to access the particular record or action; authentication alone does not grant access to every object.
  4. Return generic authentication errors to clients and keep session secrets out of logs and error details.

DynamoDB TTL is a cleanup mechanism, not an authorization check. DynamoDB removes expired items in a background process, typically within a few days. An expired record can therefore still be returned before deletion. The code above rejects it using expiresAt even while the row remains; see AWS guidance on DynamoDB TTL and filtering expired items from reads.

Best Practices for Lambda Sessions

Protect the browser credential and stored record

The session ID is a bearer credential: anyone who obtains it can act as that user until the application rejects it. Use HTTPS, a Secure and HttpOnly cookie, and an explicit SameSite=Lax or SameSite=Strict policy. The __Host- cookie prefix also requires Secure, Path=/, and no Domain attribute, which limits subdomain cookie injection. Choose SameSite based on the sign-in flow and navigation behavior; see OWASP's session cookie guidance.

Browsers attach cookies automatically, so cookie authentication also needs a CSRF design. Keep state-changing work off GET requests and use a CSRF token or strict Origin/Referer validation for protected writes. SameSite is useful defense in depth, but should not be the only defense for every application; see the OWASP CSRF prevention guidance. HttpOnly stops JavaScript from reading a cookie, but it does not stop injected script from sending requests as the logged-in user.

If the browser app and API are cross-origin, configure API Gateway CORS with the exact allowed origin and credentials support, and have the client include credentials. Cross-site cookies generally need SameSite=None; Secure. CORS controls which browser scripts may read responses; it is not a replacement for CSRF protection. AWS documents CORS for HTTP APIs.

Keep only a hash of a high-entropy random session ID in DynamoDB. Store minimal data such as the user identifier, issue time, expiry, and a version; do not store passwords or bearer tokens in the session row. DynamoDB encrypts table data at rest by default, as described in its encryption documentation. Protect access with a narrow IAM role and use TLS for service requests.

Handle failures without leaking credentials

If DynamoDB or the session service is unavailable, reject protected operations rather than treating an unverified request as authenticated. Log an outcome and request identifier for diagnosis, but redact the Cookie and Authorization headers and never log the raw token. Keep user-facing errors generic; a 401 can mean no valid session, while a service failure should produce an appropriate 5xx response.

Measure the whole request path

Initialize SDK clients outside the handler to reuse connections when Lambda reuses an environment. This does not cache user sessions. Use a primary-key lookup such as DynamoDB GetItem, keep the item small, and measure function duration and store latency before adding a cache. A cache can reduce database reads, but it also adds an invalidation path and can delay logout or revocation.

Rotate credentials and define expiry

Issue a new session ID after sign-in and after a privilege change rather than carrying forward an ID supplied before authentication. Set both an idle timeout and an absolute maximum lifetime if the product needs both; the database expiry is authoritative, and the browser cookie's Max-Age should not extend beyond it. Revoke by deleting the record, then clear the cookie. To support “sign out all devices,” design a key or index that finds only one user's active sessions instead of scanning the whole table.

Fixing Common Session Problems

Diagnose latency across Lambda and the session store

A slow session check is not necessarily a cold start. Measure Lambda initialization, handler duration, and DynamoDB or cache latency separately. Check for throttling, network configuration, oversized session items, or unnecessary reads before tuning provisioned concurrency. A warm execution environment can reuse a connection; it cannot make an external store call disappear.

Make timeout and expiry rules agree

Use one server-side expiry timestamp as the decision point and reject the session whenever it is at or before the current time. Set the TTL attribute to that timestamp so DynamoDB eventually cleans up the item. If implementing a sliding timeout, extend the server record only after an authenticated activity that merits renewal and send a matching cookie lifetime. A stale browser cookie or a pending TTL deletion must never extend authorization.

Handle concurrent updates and logout races

Two requests can read the same session version and then overwrite each other's changes. For mutable session data or sliding expiry, use an atomic update with a condition on the expected version and increment that version; if the condition fails, reload and retry only if the operation remains safe. DynamoDB evaluates condition expressions as part of the write; see the condition-expression guide. Do not renew with an unconditional PutItem: a request racing with sign-out could recreate a deleted session. A renewal should require that the item still exists, is not expired, and has the expected version.

A request that passed authorization just before sign-out may already be running when the session is deleted. If that matters for a high-impact operation, recheck authorization at the operation boundary or serialize the operation with the session change. For revocation-sensitive checks, the example uses a strongly consistent base-table read; see the GetItem consistency options. If requests can be served in multiple Regions, account for the replication consistency of the chosen DynamoDB global table mode; a write in one Region may not be visible immediately in another when using the default multi-Region eventual consistency mode, as AWS explains in its global tables documentation.

Conclusion

Use Cognito or another identity provider's short-lived access tokens when verified claims are enough. Use an opaque, server-side session when the application needs mutable session data or the ability to invalidate a session record before the browser cookie expires. Keep durable state outside Lambda, enforce expiry on every lookup, secure browser cookies against theft and CSRF, and treat authorizer caching as part of the revocation policy.

FAQs

Can AWS Lambda maintain state?

Lambda may reuse an execution environment, but the application should behave as if it exists for one invocation. Reuse module-level SDK clients or connections where appropriate; store user sessions and other durable state in a database or another shared service.

Are Lambda functions stateful?

Not as a durable application guarantee. A global variable or file in /tmp can remain available if an environment is reused, but another request can run elsewhere or after that environment is removed. Never use that behavior for authentication, authorization, or data correctness.

Does DynamoDB TTL log a user out at the expiry time?

No. TTL deletes expired records asynchronously, typically within a few days. The application must compare the stored expiry with the current time and reject an expired session even if DynamoDB still returns the item.

Can API Gateway manage sessions?

API Gateway can validate supported tokens, invoke an authorizer, route a request to Lambda, and pass or set cookies for an HTTP API integration. The application still needs a token or an external session store, and must define expiry, revocation, and authorization behavior.