RomM Authentication System: How It Handles OAuth2, Basic Auth, OIDC, and Session Logins

RomM uses a layered hybrid authentication backend that unifies session-based logins, HTTP Basic Auth, OAuth2 JWT tokens, and OpenID Connect (OIDC) into a single permission system, inspecting requests for session cookies first, then Authorization headers, and validating credentials against Redis-backed sessions or cryptographically signed tokens.

RomM is a self-hosted game library manager that protects your collection through a flexible authentication architecture. The RomM authentication system implements four distinct mechanisms—session-based logins, HTTP Basic Auth, OAuth2 password grants, and OIDC—allowing users to access their libraries via browser sessions, API clients, or external identity providers.

The Hybrid Authentication Backend

At the core of RomM’s security lies the HybridAuthBackend class in backend/handler/auth/hybrid_auth.py. This backend inspects every incoming request in a strict priority order: first checking for an existing server-side session cookie, then parsing the Authorization header for Basic or Bearer schemes, and finally falling back to kiosk mode. This design ensures that web users enjoy seamless cookie-based sessions while API clients can leverage stateless OAuth2 tokens or Basic Auth credentials.

Session-Based Authentication

Session-based login is the primary mechanism for web browser access, leveraging Redis-backed storage for scalability and security.

Login Flow and Session Creation

When a user submits credentials to the POST /login endpoint in backend/endpoints/auth.py, the system uses fastapi.security.http.HTTPBasic to extract the username and password. The AuthHandler.authenticate_user method verifies these credentials against the database. Upon successful validation, the server creates a session by setting request.session["iss"] = "romm:auth" and request.session["sub"] = user.username, along with a unique web device identifier tracked via backend/utils/auth.py.

Session Validation

On subsequent requests, HybridAuthBackend detects the session cookie and invokes AuthHandler.get_current_active_user_from_session. This retrieves the user from the Redis store, validates the session issuer, and returns an AuthCredentials object populated with the user’s OAuth scopes. The session persists until explicitly logged out or expired.

HTTP Basic Authentication

RomM supports per-request HTTP Basic Auth for API clients and direct integrations. When HybridAuthBackend detects an Authorization header with scheme.lower() == "basic", it extracts the Base64-encoded credentials and validates them using AuthHandler.authenticate_user. Unlike session-based auth, Basic Auth validates credentials on every request without establishing server-side state, making it suitable for stateless scripts and third-party tools that lack cookie support.

OAuth 2.0 JWT Implementation

For API access and mobile clients, RomM implements a full OAuth2 flow with JWT tokens, handled by the OAuthHandler class in backend/handler/auth/base_handler.py.

Token Endpoint and Password Grants

The POST /token endpoint in backend/endpoints/auth.py supports two grant types: password and refresh_token. When a client sends grant_type=password with username and credentials, the system validates the user and generates token pairs. The access token uses a type claim of "access" and embeds the user’s sub (username) and requested scopes. The refresh token uses a type claim of "refresh" and is stored in Redis under the key refresh-jti for revocation support.

JWT Structure and Validation

Both tokens are signed using the ROMM_AUTH_SECRET_KEY environment variable. When a client presents an Authorization: Bearer <token> header, HybridAuthBackend delegates to OAuthHandler.get_current_active_user_from_bearer_token. This method validates the JWT signature, checks the expiry timestamp, verifies the issuer claim matches "romm:oauth", and confirms the token type is "access" before returning the user and their overlapping scopes.

Refresh Token Storage

RomM stores refresh tokens server-side in Redis rather than using stateless JWTs for long-term sessions. This allows administrators to revoke sessions immediately by deleting the refresh-jti entry, forcing the client to re-authenticate with credentials.

OpenID Connect (OIDC) Integration

RomM supports modern single sign-on through OpenID Connect, implemented in backend/endpoints/auth.py and backend/handler/auth/base_handler.py via the OpenIDHandler class.

OIDC Login Flow

When OIDC_ENABLED=True is configured, visiting /login/openid triggers oauth.openid.authorize_redirect, sending the user to the external provider. After authentication, the provider redirects to /oauth/openid, where RomM validates the ID token’s claims—specifically requiring a verified email and extracting the username attribute. If OIDC_ALLOW_REGISTRATION is enabled, new users are provisioned automatically. The validated user is then injected into a standard RomM session exactly like a password login, setting request.session["iss"] = "romm:auth" and tracking the device.

RP-Initiated Logout

For providers supporting RP-initiated logout, RomM stores the ID token in the session as oidc_id_token. When the user hits /logout, the system extracts this token and redirects the browser to the provider’s end-session URL, ensuring single sign-out across applications.

Authentication Request Flow

Understanding the request lifecycle helps debug access issues. When HybridAuthBackend processes a request, it follows this strict cascade:

  1. Session Cookie: Checks for romm_session and validates against Redis via AuthHandler.get_current_active_user_from_session.
  2. Authorization Header: Detects Basic schemes (validating per-request) or Bearer schemes (validating JWTs via OAuthHandler).
  3. Kiosk Mode: Falls back to public access if enabled.
  4. Deny: Returns 401 if no valid credentials are found.

Code Examples

The following examples demonstrate authentication against a local RomM instance:


# 1. Session login (creates cookie file)

curl -u alice:secret -X POST http://localhost:8000/login -c cookies.txt

# 2. Basic auth on protected endpoint

curl -u alice:secret -X GET http://localhost:8000/api/games

# 3. OAuth2 password grant

curl -X POST http://localhost:8000/token \
  -d 'grant_type=password&username=alice&password=secret&scopes=read' \
  -H 'Content-Type: application/x-www-form-urlencoded'

# 4. Using the access token

curl -H "Authorization: Bearer <access_token>" http://localhost:8000/api/games

# 5. OIDC login (browser flow)

open http://localhost:8000/login/openid

# After provider redirect, session is established automatically

Summary

  • RomM authentication system uses a HybridAuthBackend that prioritizes session cookies, then Authorization headers, enforcing a unified permission layer across all mechanisms.
  • Session-based auth creates Redis-backed sessions with iss and sub claims, tracked via web device IDs in backend/utils/auth.py.
  • HTTP Basic Auth validates credentials on every request without server-side state, suitable for simple API clients.
  • OAuth2 implements password and refresh token grants, storing refresh tokens in Redis (refresh-jti) while signing access tokens with ROMM_AUTH_SECRET_KEY.
  • OIDC enables external provider login via /login/openid, converting verified ID tokens into local sessions and supporting RP-initiated logout through stored oidc_id_token values.

Frequently Asked Questions

How does RomM decide which authentication method to use?

RomM’s HybridAuthBackend inspects requests in a fixed priority order: first checking for a valid session cookie, then parsing the Authorization header for Basic or Bearer schemes, and finally checking for kiosk mode. This ensures web browsers use seamless sessions while API clients can force specific auth methods via headers.

What OAuth2 grants does RomM support?

According to the source code in backend/endpoints/auth.py, RomM supports the password grant for initial token exchange and the refresh_token grant for obtaining new access tokens without re-entering credentials. The implementation does not currently support authorization code or client credentials grants.

How does RomM handle OIDC user registration?

When OIDC_ALLOW_REGISTRATION is enabled in the configuration, the OpenIDHandler automatically creates a local user account after validating the ID token’s email and username claims. If disabled, only existing users with matching usernames can authenticate via OIDC, and unknown users are rejected during the callback to /oauth/openid.

Where are OAuth2 refresh tokens stored in RomM?

Unlike access tokens, which are stateless JWTs, refresh tokens are stored server-side in Redis with keys prefixed by refresh-jti. This storage mechanism allows administrators to revoke sessions immediately by deleting the Redis entry, forcing the client to re-authenticate with their username and password.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →