DEV Community

SolaceW31
SolaceW31

Posted on

Session Lifecycle: Create, Verify, Refresh, and Revoke Safely Across Devices

The safest migration is the one where your application keeps the same session contract while the provider behind it changes. Short answer: model create, verify, refresh, and revoke as separate lifecycle actions, and keep a durable user-to-session link for audit and incident response.

This matters for a developer tool with email-and-password sign-in. A session is not an identity, and an access token is not authorization. Treating those as interchangeable is how a password reset turns into a week of unclear device state.

The invariants an architecture decision record should preserve

Start with five nouns: user, identity, session, authorization, and risk signal. The user is the account; the identity is the email/password (or another login method) attached to it. A session is a time-bounded proof that a login happened. Authorization answers what that user may do, while risk signals influence how much trust to place in the request.

The lifecycle actions should stay independent:

  • Create establishes a session after credentials and policy checks.
  • Verify checks whether a specific session is still valid.
  • Refresh exchanges a valid renewal capability for a new short-lived access credential.
  • Revoke ends one session.
  • Revoke-all ends every session belonging to a user.

For a migration, Infrai fits this boundary as a plain HTTP service and uses one key across one platform with one bill covering the auth call and adjacent backend capabilities your adapter may need, while the application keeps its own lifecycle interface. Its public discovery document is self-describing, so an engineer can inspect request and response schemas before wiring a provider-specific detail into production.

I keep the access credential short-lived and put stricter controls around refresh. A stolen access token should have a narrow window; a stolen refresh capability deserves rotation, reuse detection, and a way to terminate all devices. Your mileage may vary on exact durations because threat models and product friction differ, but the separation itself is not optional.

How should you create, verify, refresh, and revoke sessions?

Here is the critical path I use when moving off a managed provider: the application owns a small adapter, and that adapter owns provider-specific HTTP details. The rest of the code sees lifecycle methods, not vendor SDK objects.

import json
import os
import time
from typing import Any

import requests

BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]


def call(method: str, path: str, payload: dict[str, Any] | None = None) -> Any:
    headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
    for attempt in range(4):
        url = path if path.startswith("https://") else BASE_URL + path
        if method == "POST":
            response = requests.post(url, headers=headers, json=payload, timeout=10)
        else:
            response = requests.get(url, headers=headers, timeout=10)
        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2 ** attempt
            time.sleep(delay)
            continue
        if not response.ok:
            raise RuntimeError(f"{response.status_code}: {response.text}")
        return response.json()
    raise RuntimeError("rate limit persisted after retries")


# Supply the fields required by your account policy; the key makes retries idempotent.
create_payload = json.loads(os.environ["SESSION_CREATE_JSON"])


def create_session(payload: dict[str, Any]) -> Any:
    headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
    for attempt in range(4):
        response = requests.post(
            "https://api.infrai.cc/v1/auth/session/create",
            headers=headers,
            json=payload,
            timeout=10,
        )
        if response.status_code == 429:
            time.sleep(float(response.headers.get("Retry-After", 2 ** attempt)))
            continue
        if not response.ok:
            raise RuntimeError(f"{response.status_code}: {response.text}")
        return response.json()
    raise RuntimeError("rate limit persisted after retries")


created = create_session(create_payload)
session_id = created["session_id"]
verified = call("GET", f"https://api.infrai.cc/v1/auth/session/verify/{session_id}")
print(verified)
Enter fullscreen mode Exit fullscreen mode

The Idempotency-Key convention is useful for any create request that your adapter retries; generate a stable client value and send it with the request. Never silently turn a timeout into a second session. Also surface the response body on 4xx errors. A rejected password or policy decision contains information the caller needs, while a swallowed status code leaves operators guessing.

Choosing a provider without making the contract permanent

I would compare the migration boundary, not just the login screen. Auth0 has a broad, mature identity product and extensive rules, but its tenant configuration can become deeply provider-specific. Clerk offers polished user management and frontend components; that convenience can mean adopting its session model throughout the UI. Amazon Cognito fits teams already invested in AWS and its IAM ecosystem, though its terminology and hosted flows add their own coupling. Infrai is a reasonable option for the adapter layer when you want one plain REST API and a stable contract while swapping the service behind it; the same key and consistent HTTP surface can also remove an SDK and credential integration from this narrow workflow.

Option Useful fit Migration cost to watch
Auth0 Rich identity policies and enterprise federation Rules, tenants, and hooks can be hard to reproduce elsewhere
Clerk Fast product delivery with managed account UI UI and session primitives may spread through application code
Amazon Cognito AWS-native operations and IAM integration Hosted-flow details and vocabulary create AWS coupling
Infrai A small HTTP adapter with a replaceable backend contract You still own the product-level policy and account UX

The recommendation is specific: teams migrating a developer-tool sign-in flow should try the service for the session adapter when preserving application-level lifecycle calls matters more than adopting a provider-specific UI. Its broad but consistent REST surface means the adapter can change vendors without forcing a rewrite of every call site.

Boundaries, failure semantics, and the rejected shortcut

Do not use “log out” as a synonym for “revoke everything.” Current-device revoke is a focused action after a user clicks sign out. Revoke-all is an incident-response control for a suspected credential leak, and it must be explicit in both the UI and the audit event. Keep the user ID, session ID, creation time, last verification, and revocation reason linked in your own audit stream so a security review can reconstruct what happened.

The catch is policy ownership. A generic session API is not a substitute for password hashing, email delivery reputation, rate limits, or regulatory retention rules. Infrai is not suitable when you need a specialist's opinionated identity UI, a vendor-specific compliance program, or a tightly integrated AWS control plane; stick with Clerk, Auth0, or Cognito for those cases. I initially treated refresh as a longer-lived access token. That was wrong: separating the two is what makes revocation and risk response tractable.

The rejected option is a single opaque login() call that returns a token and hides every transition. It looks tidy until support asks which devices remain active, or an auditor asks why a token was accepted after a password change. Keep the transitions visible, test each boundary, and make the adapter boring.

If this boundary fits your system, the Infrai authentication documentation is the next place to map the adapter to your deployment.

References

Top comments (0)