How to Set Up OIDC Authentication in RomM: A Complete Guide

Enable OIDC in RomM by setting OIDC_ENABLED=true and configuring your provider credentials in environment variables, then restart the services to activate the OpenID Connect login button.

RomM supports OpenID Connect (OIDC) as a first-class authentication method for seamless single sign-on (SSO) integration. When properly configured, the backend validates tokens from your identity provider using the OpenIDHandler class in backend/handler/auth/base_handler.py, automatically provisions users, and assigns roles based on OIDC claims. This guide explains how to set up OIDC authentication in RomM using environment variables and the underlying implementation.

How the OIDC Flow Works in RomM

The authentication process involves coordinated steps between the Vue frontend and Python backend. When a user clicks the OIDC login button in frontend/src/v2/components/Auth/OIDCButton.vue, the application redirects to the provider's authorization endpoint with the configured client ID, scopes, and OIDC_REDIRECT_URI.

After the provider authenticates the user and redirects back to RomM, the backend exchanges the authorization code for tokens. The OpenIDHandler.get_current_active_user_from_openid_token method in backend/handler/auth/base_handler.py validates the ID token, checks that the email is verified, and extracts the username using the OIDC_USERNAME_ATTRIBUTE claim (defaulting to preferred_username).

If the email does not exist in the database and OIDC_ALLOW_REGISTRATION is true, RomM creates a new user record with a randomly generated password. The handler also maps OIDC role claims to RomM roles (ADMIN or USER) based on your OIDC_CLAIM_ROLES configuration. Finally, the backend creates a signed session cookie using ROMM_AUTH_SECRET_KEY that the frontend consumes via the /api/me endpoint.

Required Environment Variables

All OIDC configuration is loaded from environment variables via the config manager. Add these entries to your .env file or Docker Compose environment:

  • OIDC_ENABLED: Set to true to activate the OIDC subsystem
  • OIDC_PROVIDER: Human-readable name displayed in the UI (e.g., "Keycloak")
  • OIDC_CLIENT_ID: Your OIDC client identifier from the provider
  • OIDC_CLIENT_SECRET: The client secret associated with your OIDC client
  • OIDC_REDIRECT_URI: Full callback URL (e.g., https://my-romm.tld/auth/openid/callback)
  • OIDC_USERNAME_ATTRIBUTE: Claim field containing the username (default: preferred_username)
  • OIDC_ALLOW_REGISTRATION: Set to true to auto-create users for unknown email addresses
  • OIDC_CLAIM_ROLES: Optional claim name containing user role strings
  • OIDC_ROLE_ADMIN: Value in the role claim that grants ADMIN rights
  • OIDC_ROLE_EDITOR and OIDC_ROLE_VIEWER: Legacy values that map to the USER role
  • ROMM_AUTH_SECRET_KEY: Required secret for signing JWTs and session cookies (already required for standard auth)

These variables are imported at the top of backend/handler/auth/base_handler.py (lines 15-23) and control the behavior of the OpenIDHandler class.

Step-by-Step Setup Guide

  1. Create an OIDC client in your Identity Provider (Keycloak, Auth0, Google Workspace, Azure AD, etc.) and set the redirect URI to match your OIDC_REDIRECT_URI value exactly.

  2. Configure your RomM .env file with the OIDC variables listed above.

  3. Restart the RomM container to load the new environment variables.

  4. Verify the login screen displays the "Login with [Provider]" button, which is conditionally rendered in frontend/src/v2/views/Auth/Login.vue when OIDC is enabled.

  5. Test the authentication flow by clicking the OIDC button, authenticating with your provider, and verifying you are redirected back to RomM as the authenticated user.

  6. Optional: Configure logout redirect by ensuring your provider supplies an oidc_logout_url claim in the token, which RomM stores in the OIDCLogoutResponse model and uses when users click logout.

Code Implementation Details

Backend Token Validation

The core validation logic resides in backend/handler/auth/base_handler.py. The handler checks if OIDC_ENABLED is active, validates the email is verified, extracts the preferred username, and maps roles:


# backend/handler/auth/base_handler.py (excerpt)

class OpenIDHandler:
    async def get_current_active_user_from_openid_token(self, token: Any):
        if not OIDC_ENABLED:
            return None, None

        userinfo = token.get("userinfo")
        if userinfo is None:
            raise HTTPException(status_code=400, detail="Userinfo is missing from token.")

        email = userinfo.get("email")
        if email is None:
            raise HTTPException(status_code=400, detail="Email is missing from token.")

        # Verify email is confirmed

        if not self._email_is_verified(userinfo):
            raise HTTPException(status_code=400, detail="Email is not verified.")

        # Extract username and map roles

        username = userinfo.get(OIDC_USERNAME_ATTRIBUTE)
        role = self._map_roles(userinfo)

        # Create or update user record

        user = db_user_handler.get_user_by_email(email)
        if user is None:
            if not OIDC_ALLOW_REGISTRATION:
                raise HTTPException(status_code=403, detail="Registration disabled.")
            user = db_user_handler.add_user(
                User(username=username, email=email, role=role, hashed_password=str(uuid.uuid4()))
            )
        elif role != user.role:
            user = db_user_handler.update_user(user.id, {"role": role})

        if not user.enabled:
            raise UserDisabledException

        return user, userinfo

Frontend Integration

The login button is implemented in frontend/src/v2/components/Auth/OIDCButton.vue, which initiates the flow by calling the authentication store:

<!-- frontend/src/v2/components/Auth/OIDCButton.vue -->
<template>
  <v-btn @click="login" :loading="loading">
    {{ $t('login.login-oidc', { oidc: provider || 'OIDC' }) }}
  </v-btn>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useAuthStore } from '@/stores/auth';

const provider = import.meta.env.VITE_OIDC_PROVIDER;
const loading = ref(false);
const auth = useAuthStore();

function login() {
  loading.value = true;
  auth.startOidcLogin();
}
</script>

Environment Configuration Example


# Core auth secret (required for all auth methods)

ROMM_AUTH_SECRET_KEY=super-secret-key

# OIDC configuration

OIDC_ENABLED=true
OIDC_PROVIDER=Keycloak
OIDC_CLIENT_ID=romm-client
OIDC_CLIENT_SECRET=xxxxxxxxxxxx
OIDC_REDIRECT_URI=https://my-romm.tld/auth/openid/callback
OIDC_USERNAME_ATTRIBUTE=preferred_username
OIDC_ALLOW_REGISTRATION=true
OIDC_CLAIM_ROLES=roles
OIDC_ROLE_ADMIN=admin
OIDC_ROLE_EDITOR=editor
OIDC_ROLE_VIEWER=viewer

Summary

  • RomM supports OIDC through environment variables defined in backend/handler/auth/base_handler.py
  • Set OIDC_ENABLED=true and configure provider credentials to activate the feature
  • The system validates tokens via OpenIDHandler.get_current_active_user_from_openid_token and auto-provisions users when OIDC_ALLOW_REGISTRATION is enabled
  • Roles map from OIDC claims using OIDC_CLAIM_ROLES and the specific role environment variables (OIDC_ROLE_ADMIN, etc.)
  • The frontend button in OIDCButton.vue initiates the flow, while the OIDCLogoutResponse model handles optional provider logout redirects

Frequently Asked Questions

What identity providers work with RomM OIDC?

Any standards-compliant OpenID Connect provider works, including Keycloak, Auth0, Google Workspace, Azure AD, and Okta. Configure the authorization endpoint and token endpoint in your provider settings, then map the client ID and secret into RomM's environment variables.

Why does RomM require ROMM_AUTH_SECRET_KEY for OIDC?

RomM uses this secret to sign the session cookies and JWT tokens after successful OIDC validation. This is required even when using external authentication to maintain secure session state between the frontend and backend API.

How do I map admin roles from my OIDC provider?

Set OIDC_CLAIM_ROLES to the claim name containing roles (e.g., "groups" or "roles"), then set OIDC_ROLE_ADMIN to the specific value that should grant administrator privileges (e.g., "admin" or "romm-admin"). Users with matching claims receive the ADMIN role in RomM, while other values map to USER or are ignored if not configured.

Can I disable local authentication when OIDC is enabled?

Currently, RomM supports both authentication methods simultaneously. The OIDC button appears alongside the standard login form when OIDC_ENABLED is true, allowing users to choose their preferred method. There is no configuration option to disable local authentication while OIDC is active.

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 →