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

> Discover how RomM's hybrid auth system unifies session logins, Basic Auth, OAuth2 JWT, and OIDC. Learn about its request inspection and validation process against Redis sessions and signed tokens.

- Repository: [The RomM Project/romm](https://github.com/rommapp/romm)
- Tags: deep-dive
- Published: 2026-07-06

---

**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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/backend/handler/auth/base_handler.py).

### Token Endpoint and Password Grants

The `POST /token` endpoint in [`backend/endpoints/auth.py`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/backend/endpoints/auth.py) and [`backend/handler/auth/base_handler.py`](https://github.com/rommapp/romm/blob/main/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:

```bash

# 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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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.