# Authentication Configure user authentication and access control for MyGarage. Choose between no authentication, local JWT authentication, or OIDC/SSO integration. --- ## Authentication Modes MyGarage supports three authentication modes: | Mode | Use Case | Security Level | Setup Complexity | |------|----------|----------------|------------------| | **None** | Local, testing, single-user behind firewall | ⚠️ Low | Easy | | **Local** | Single-user or family deployments, traditional login | ✅ High | Easy | | **OIDC** | A household that already runs an identity provider (single sign-on) | ✅ Very High | Medium | --- ## No Authentication Mode (Default) ### Overview - **Default mode** on first install - No login required - anyone with URL can access - ⚠️ **NOT RECOMMENDED for public-facing deployments** - Best for local testing or behind trusted reverse proxy with external auth ### Security Warnings When running in `none` mode, MyGarage: - Logs "⚠️ SECURITY WARNING: Authentication is disabled (auth_mode='none')" at startup - Shows **No Authentication Enabled** in the **Authentication Mode** card (Settings → System), unless it detects an authenticating reverse proxy ### When to Use ✅ **Appropriate**: - Local development (`localhost` only) - Single-user homelab behind firewall - Behind reverse proxy with authentication (Authelia, oauth2-proxy) - Testing/demo environments ❌ **Never Use When**: - Exposed to public internet - Contains sensitive data (loan docs, insurance) - Multiple users accessing ### Configuration After MyGarage is running, set the authentication mode in the web UI: 1. Visit **Settings → System** 2. In the **Authentication Mode** card, click the **None** tab, then **Turn off sign-in**, and confirm 3. It takes effect at once --- ## Local Authentication Mode ### Overview - Traditional username/password authentication - JWT-based sessions with HttpOnly cookies - Argon2id password hashing (100MB memory cost, time cost 2) - Multi-user support with role-based access control - Recommended for most self-hosted deployments ### Security Features - ✅ **Argon2id Hashing** - Industry-leading password hashing - ✅ **JWT Tokens** - Stateless session management - ✅ **HttpOnly Cookies** - Prevent XSS attacks - ✅ **SameSite Protection** - CSRF mitigation - ✅ **Rate Limiting** - 5 login attempts per minute - ✅ **Secure Flag** - Auto-enabled in production (HTTPS) ### Enabling Local Auth **Via Web UI**: 1. Visit **Settings → System** 2. In the **Authentication Mode** card, click the **Local** tab, then **Switch to local sign-in**, and confirm 3. It takes effect at once 4. The sign-in page opens. Click **Register here** to create the first account **JWT Secret**: Auto-generated and saved to `/data/secret.key` on first startup. **Back up this file** — losing it invalidates all existing login sessions. **Optional Environment Variable** (Advanced): ```bash JWT_COOKIE_SECURE=auto # auto (default), true (HTTPS only), false (HTTP allowed) ``` ### First User Registration After enabling local auth: 1. Visit the web interface 2. On the sign-in page, click **Register here** 3. Fill in: - **Username** - Used to sign in - **Email** - **Password** - **Confirm Password** 4. Click **"Create Account"** 5. First user automatically becomes **admin** Registration is only open while there are no users. After that, an admin adds users (below). ### Password Requirements 8 to 100 characters, with at least one each of: - Uppercase letter - Lowercase letter - Number - Special character (`!@#$%^&*(),.?":{}|<>`) ### User Management #### Adding Users (Admin Only) 1. Login as admin 2. Go to **Settings → System** and click **Manage Family** in the **Family Management** card 3. Turn on **Multi-user mode** (needed once, before adding users) 4. Click **"Add User"** 5. Fill in: - **Username**, **Email**, **Password**, **Confirm Password** - **Role** - User or Admin - **Active User** - Inactive users cannot log in, including SSO users; disabling blocks their signed-in requests at once 6. Click **"Create"** 7. Share credentials with new user (securely!) #### User Roles **Admin**: - ✅ Access all vehicles - ✅ Manage users (add, edit, delete) - ✅ Configure system settings - ✅ View all service records and documents - ✅ Export all data **Standard User**: - ✅ Access their own vehicles and vehicles shared with them - ✅ Add/edit records on vehicles they own or have write access to - ❌ Cannot manage users - ❌ Cannot change instance settings: Settings shows only their own cards, and the File Management, Notifications and Backup & Restore tabs are hidden - ❌ Cannot access other users' vehicles unless shared #### Shared Vehicles A vehicle can be shared with other users: 1. Go to vehicle detail page 2. Click **"Share"** 3. Click **"Share with another user"**, pick the user and **Read** or **Write** permission 4. Click **"Share"** See [Family Multi-User](Family-Multi-User) for what each permission allows. #### Password Reset (Admin) If user forgets password: 1. Login as admin 2. Go to **Settings → System → Manage Family** 3. Click the key icon (**Reset password**) on the user's card 4. Enter a new password and confirm it 5. Click **"Update"** This works for local accounts only, not OIDC users. #### Password Change (Admin) An admin changes their own password under **Settings → System → Manage Family → Configure Auth**, in the **Change Password** section: enter the current password and the new one twice. --- ## OIDC/SSO Authentication ### Overview - Integrate with external identity providers - Centralized user management - Single Sign-On (SSO) across services - Supports: Authentik, Keycloak, Rauthy, Auth0, Okta, Azure AD, Google Workspace, and any standards-compliant OIDC provider ### Supported OIDC Providers MyGarage is a standard OIDC client, so any provider that meets the requirements below works. These are the ones the in-app setup lists: - **Authentik** - **Keycloak** - **Rauthy** - **Auth0** - **Okta** - **Azure AD (Entra ID)** - **Google Workspace** ### OIDC Requirements Your OIDC provider must support: - OpenID Connect (OIDC) protocol - Authorization Code Flow - ID tokens with `email` claim ### Self-Hosted Provider on a Private IP MyGarage refuses to fetch OIDC metadata from an issuer that resolves to a private/internal IP (SSRF protection). If your provider is self-hosted and its public hostname resolves to a private/LAN address on your network (split-horizon DNS), allow-list that hostname: ```bash MYGARAGE_TRUSTED_HOSTS=auth.example.com # comma-separated hostnames, IPs, or CIDRs ``` This relaxes the private-IP check for the listed hosts only; all other SSRF protections remain in effect. When this check blocks the issuer, **Test Connection** says so and names `MYGARAGE_TRUSTED_HOSTS`. ### Enabling OIDC Auth **Via Web UI**: 1. Visit **Settings → System** 2. In the **Authentication Mode** card, click the **OIDC** tab, then **Configure OIDC** 3. Fill in the fields: - **Provider Name** - Display name (e.g., "Authentik") - **Issuer URL** - Your OIDC provider's issuer endpoint - **Client ID** - From your identity provider - **Client Secret** - From your identity provider - **Callback URL** - Leave blank to build it from each request, or enter your public URL behind a reverse proxy. **Copy** it into your provider's redirect URIs. - **Scopes** - Required scopes (default: `openid profile email`) 4. Click **"Test Connection"** to check MyGarage can reach the provider 5. Click **Save** 6. Back on the Authentication Mode card, click **Switch to SSO** and confirm. It takes effect at once 7. Visit the web interface → click **"Sign in with "** ### Keeping SSO Sign-In Usable Sign-in can only be set to SSO while SSO is on with an issuer URL and client ID, and SSO can't be turned off or lose either while sign-in uses it, so a save can't lock everyone out. The settings API, **Configure OIDC** and a settings restore answer 409 instead. ### Configuration Examples #### Authentik 1. **Create an application + provider in Authentik**: - Applications → Applications → Create; Name: `MyGarage`, Slug: `mygarage` - Add a new OAuth2/OIDC provider, Client Type: **Confidential** - Redirect URI: copy the **Callback URL** shown in MyGarage's OIDC settings 2. **MyGarage UI Configuration** (Settings → System → Configure OIDC): - **Provider Name**: `Authentik` - **Issuer URL**: `https://auth.example.com/application/o/mygarage/` - **Client ID** / **Client Secret**: from the Authentik provider - **Scopes**: `openid profile email` #### Keycloak 1. **Create Client in Keycloak**: - Go to **Clients → Create Client** - Client ID: `mygarage` - Client Protocol: `openid-connect` - Access Type: **Confidential** 2. **Configure Client**: - Valid Redirect URIs: Copy from MyGarage UI after configuration - Web Origins: `https://mygarage.example.com` - Get client secret from **Credentials** tab (copy this!) 3. **MyGarage UI Configuration** (Settings → System → Configure OIDC): - **Provider Name**: `Keycloak` - **Issuer URL**: `https://keycloak.example.com/realms/myrealm` - **Client ID**: `mygarage` - **Client Secret**: Paste from Keycloak - **Scopes**: `openid profile email` - Copy **Callback URL** and add to Keycloak client #### Google Workspace 1. **Create OAuth 2.0 Client in Google Cloud Console**: - Go to https://console.cloud.google.com/apis/credentials - **Create Credentials → OAuth 2.0 Client ID** - Application type: **Web application** - Authorized redirect URIs: Copy from MyGarage UI after configuration 2. **Copy Client ID and Secret** (shown after creation) 3. **MyGarage UI Configuration** (Settings → System → Configure OIDC): - **Provider Name**: `Google` - **Issuer URL**: `https://accounts.google.com` - **Client ID**: Paste from Google (e.g., `123456.apps.googleusercontent.com`) - **Client Secret**: Paste from Google - **Scopes**: `openid profile email` - Copy **Callback URL** and add to Google Cloud Console #### Azure AD (Entra ID) 1. **Register Application in Azure Portal**: - Go to **Azure Active Directory → App registrations** - Click **"New registration"** - Name: MyGarage - Redirect URI: Copy from MyGarage UI after configuration 2. **Configure Application**: - Copy **Application (client) ID** - Copy **Directory (tenant) ID** - Go to **Certificates & secrets** → Create new client secret - Copy secret value (shown only once!) 3. **MyGarage UI Configuration** (Settings → System → Configure OIDC): - **Provider Name**: `Azure AD` - **Issuer URL**: `https://login.microsoftonline.com//v2.0` (replace ``) - **Client ID**: Paste Application (client) ID - **Client Secret**: Paste client secret value - **Scopes**: `openid profile email` - Copy **Callback URL** and add to Azure app registration ### OIDC User Management #### Automatic User Creation When a user logs in via OIDC for the first time (with **Auto-create users on first login** on, the default): - MyGarage creates user account automatically - Email from OIDC `email` claim is used - First user becomes **admin** - Subsequent users are **standard users** #### Role Assignment **Option 1: Manual (in MyGarage)**: - Admin logs in - Goes to **Settings → System → Manage Family** - Edits the user (**Edit user**) and sets **Role** (User or Admin) **Option 2: OIDC Admin Group**: In **Settings → System → Configure OIDC**, set **Admin Group (Optional)** to a group name from your provider's `groups` claim. Users in that group get admin when their account is created; everyone else is a standard user. #### Managing SSO Users SSO users are managed in **Settings → System → Manage Family** like local users, and their cards carry an **OIDC** badge. The differences: - **Edit user** locks their full name, since the identity provider overwrites it on every sign-in, and names the provider. Email, role, relationship and **Active User** can be changed. - There is no password reset for them. - To disable one, untick **Active User** or click the power icon (**Disable user**) on their card. That blocks their MyGarage requests at once. It doesn't end their session, so re-enabling them before it expires lets it work again. - Disabling a user only at the identity provider leaves a MyGarage session they already have valid until it expires (2 hours by default). - **Disable or delete?** Deleting removes the account and its vehicles. If **Auto-create users on first login** is on, their next SSO login creates a fresh account. To keep someone out, disable them instead. As with any account, MyGarage won't disable, demote or delete the last active admin. #### Relinking an SSO Account If a user's account at the identity provider is re-created (a provider reinstall or swap, or the user removed and added back), MyGarage no longer recognizes their sign-in. It won't relink the account on an email or username match, so it refuses the login and the sign-in page tells them to ask an administrator. An admin lets them back in: 1. Go to **Settings → System → Manage Family** 2. Find the user's own card and make sure the account is theirs. Click the link icon (**Allow SSO relink for 30 minutes**) on it. The card shows a **Relink open until** badge with the closing time. 3. Have the user sign in with SSO within 30 minutes. The first SSO login whose email or username matches the account links it without asking for a password, and the relink closes. Pick the card by the person, never by searching for the email in their complaint. If a local account holds someone else's email, that account is the problem: fix it first, and don't relink it. Click the icon again (**Cancel SSO relink**) to close it early. A disabled account can't be relinked, so its card doesn't offer one until it is enabled again. One already open can still be cancelled. Allowing, cancelling and using a relink are recorded in the audit log, as are refused SSO logins. A used relink is an `oidc_relink_used` row with the IP address and user agent of the sign-in that used it. Behind a reverse proxy, the IP is the proxy's. While a relink is open, any identity-provider account presenting that email or username links without a password. If your provider lets users set their own unverified email, allow the relink only when the user is ready to sign in. Any SSO sign-in to the account closes the relink, so once the user is in, the badge is gone. The card doesn't update on its own, so reload Manage Family to check. If the badge is gone and the user still can't sign in, look in the audit log. An `oidc_relink_used` row you didn't expect means someone else used the relink: investigate, then allow it again with the user ready to sign in at once. No row means it expired, or another admin cancelled it (an `oidc_relink_revoked` row): allow it again. On SQLite, this lists the latest of both: ```bash docker exec mygarage python3 -c "import sqlite3; [print(r) for r in sqlite3.connect('/data/mygarage.db').execute(\"SELECT timestamp, action, username, ip_address, user_agent, details FROM audit_logs WHERE action IN ('oidc_relink_used', 'oidc_relink_revoked') ORDER BY id DESC LIMIT 5\")]" ``` If the locked-out user is the only admin, run the tool inside the container instead, with their MyGarage username. It writes at once, with no dry run: ```bash docker exec -w /app mygarage python tools/oidc_allow_relink.py --username X ``` `--minutes` sets how long the relink stays open, 0 to 30 (default 30), and `--minutes 0` cancels one. The tool prints the closing time in UTC. ### OIDC Troubleshooting A refused or failed SSO sign-in sends the user back to the sign-in page with a sentence saying why. A refusal names its reason, like a disabled account, or no matching account while **Auto-create users on first login** is off. Anything else says single sign-on didn't work, and `docker logs mygarage` has the cause. #### "Invalid redirect URI" Error **Cause**: Redirect URI mismatch between OIDC provider and MyGarage **Fix**: In **Settings → System → Configure OIDC**, copy the exact **Callback URL** MyGarage shows into your provider. Match protocol (`https://`), domain, and the `/api/auth/oidc/callback` path; watch trailing slashes. #### "Invalid client secret" Error **Cause**: Client secret incorrect or expired **Fix**: Regenerate the secret in your provider, paste it into **Settings → System → Configure OIDC**, and click **Save**. #### "OIDC claims missing email" Error **Cause**: OIDC provider not returning email in ID token **Fix**: 1. Ensure `email` scope is requested 2. Configure OIDC provider to include `email` claim in ID token 3. Some providers require explicit consent for email scope --- ## Switching Authentication Modes ### From None → Local 1. Visit **Settings → System** 2. In the **Authentication Mode** card, click the **Local** tab, then **Switch to local sign-in**, and confirm. It takes effect at once 3. On the sign-in page that opens, click **Register here** to create the first user ### From None → OIDC 1. Configure OIDC provider (create client - see examples above) 2. Visit **Settings → System** 3. In the **Authentication Mode** card, click the **OIDC** tab, then **Configure OIDC** 4. Configure OIDC settings (provider, client ID, secret, etc.), click **Test Connection**, then **Save** 5. Back on the Authentication Mode card, click **Switch to SSO** and confirm. It takes effect at once 6. Visit web interface → Click **"Sign in with "** If SSO sign-in fails and nobody has an account yet, click **Register here** on the sign-in page instead. The first account becomes the admin. ### From Local → OIDC Existing local accounts are kept and linked on each user's first OIDC sign-in: - If the OIDC email or username matches a local account, MyGarage asks for that account's local password once to link it (**Link Account**). An account already linked to another sign-in, or one without a password, needs an administrator. Accounts with a password can still sign in with it alongside SSO. For an account that needs an administrator, see [Relinking an SSO Account](#relinking-an-sso-account). --- ## Session Management ### JWT Token Expiration - **Default lifetime**: 2 hours - Sessions expire after this window; users must re-authenticate. ### Logout Users can manually logout: 1. Click the gear icon in the top bar (**Quick settings**), or the logout button beside it on desktop 2. **"Logout"** 3. JWT cookie deleted 4. Redirected to login page ### Session Timeout If inactive for longer than JWT expiration: - Session expires - User redirected to login - Must re-authenticate --- ## Security Best Practices ### JWT Secret - Auto-generated and stored at `/data/secret.key` — no manual setup needed. - **Back up `/data/secret.key`**: losing it invalidates all existing login sessions. - Unique per instance. Only set `MYGARAGE_SECRET_KEY` if you must share one secret across replicas. ### Settings Encryption Key MyGarage stores its secret settings encrypted (AES-256-GCM). That covers the SSO client secret, the notification tokens, the webhook URLs and SMTP password, the LLM and POI provider keys, the LiveLink MQTT password and the webhook ingest token. The key comes from the first of these that is set: 1. `MYGARAGE_SETTINGS_KEY`: the key itself. 2. `MYGARAGE_SETTINGS_KEY_FILE`: the absolute path of a file holding it (for Docker secrets). A relative path is refused. 3. `/data/settings.key`: generated on the first start when neither variable is set. The first one that is set wins, even when its key is wrong: MyGarage never falls back to the next one. So when you switch to a variable, put the key already in use into it, not a new one. A new key makes every stored secret unreadable. **Back up `settings.key`, and keep it apart from your backups.** Neither the settings backup nor the full backup includes it, on purpose. On a new host without it, a full restore brings every secret back unreadable and a settings restore skips them, so you re-enter them. A full restore on a new host also needs one sign-in, because `secret.key` isn't in the backup either. A key is a base64url-encoded 32-byte value. To make one: ```bash python3 -c "import secrets; print(secrets.token_urlsafe(32))" ``` **Keeping the key off the data volume (Docker secrets):** copy your current `/data/settings.key` into a secret file, and mount it: ```yaml services: mygarage: environment: MYGARAGE_SETTINGS_KEY_FILE: /run/secrets/mygarage_settings_key secrets: - mygarage_settings_key secrets: mygarage_settings_key: file: ./secrets/mygarage_settings_key ``` Compose mounts a file secret with its host permissions, so the file must be readable by the container's user (uid 1000, or the UID you set in `user:`). Restart, and check that the log line `Encrypted N, re-keyed M, unreadable: ...` ends in `unreadable: none`. MyGarage then ignores `/data/settings.key`. Once you've kept a copy somewhere safe, you can remove it from the data volume. **Rotating the key:** the file or the variable can hold several keys, separated by commas or newlines. The first one encrypts; the others only decrypt. 1. Put the new key first and the old one after it, then restart. The start re-encrypts every secret under the new key and logs how many (`re-keyed M`). 2. Check that the log line says `unreadable: none`. 3. Remove the old key and restart again. Removing a key makes every backup taken under it unreadable for secrets, even though the live settings moved to the new key. Keep retired keys somewhere safe, outside the keyring, if you may restore an old backup. **When the key is unavailable** (a variable or file holds a malformed key, `MYGARAGE_SETTINGS_KEY_FILE` points at a missing file, or `/data` isn't writable on the first start): - Settings shows a banner, and the log names the broken source (never the key). - Secrets still stored unencrypted keep working, but encrypted ones can't be read. - Saving a secret answers 503. So does creating or restoring a backup while any secret is still unencrypted. Fix the source and reload the page; no restart is needed. If a secret was encrypted under a key that is no longer in the keyring, the banner names it, and you re-enter it. **What it protects:** any copy of the secrets that leaves without the key, like a backup. Not someone who has the whole data directory with `settings.key` in it. Old plaintext can survive inside the database files; see [Database Configuration - Backups and secret settings](Database-Configuration.md#backups-and-secret-settings). ### HTTPS (Required for Production) - ✅ **Always use HTTPS** - JWT cookies require secure flag - ✅ **Use reverse proxy** - Traefik, Nginx, Caddy with Let's Encrypt - ❌ **Never use HTTP in production** - Session hijacking risk ### Password Policies - ✅ **Enforce strong passwords** - Min 12 characters, complexity - ✅ **Educate users** - No password reuse, use password manager - ✅ **Require password changes** - After security incidents - ❌ **Don't share accounts** - Each user gets own account ### Rate Limiting Built-in rate limits (per IP): - **Auth endpoints** (login, registration, password change/reset): 5 requests per minute - **SSO sign-in** (the start and the callback): 5 requests per minute - **SSO account linking** (`POST /api/auth/oidc/link-account`, the password check that links an SSO login to an existing account): 5 requests per minute Exceeding the limit returns HTTP 429 until the window passes. A limited SSO attempt goes back to the login page instead, saying "Too many sign-in attempts. Wait a minute, then try again." Behind a reverse proxy, "per IP" only means per client with `MYGARAGE_TRUSTED_PROXIES` set. Without it, MyGarage sees the proxy as every client, so everyone shares one limit. If everyone signs in through SSO and MyGarage sits behind a proxy, set it: unset, one client can block SSO sign-in for everyone for a minute. See [Client IP and Rate Limits](Reverse-Proxy#client-ip-and-rate-limits). --- ## Multi-User Scenarios ### Family Deployment - **Mode**: Local authentication - **Users**: 2-4 users (family members) - **Vehicles**: Shared (all family members see all vehicles) - **Setup**: Each family member has own account, all vehicles shared ### Household With SSO - **Mode**: OIDC, with the identity provider you already run (Authentik, Rauthy and so on) - **Users**: everyone in the household signs in through it - **Vehicles**: Admins see all, users see their own and shared vehicles - **Setup**: An OIDC admin group makes its members admins when their accounts are created --- ## Need Help? - **OIDC Configuration Issues** - See [Troubleshooting Guide](Troubleshooting.md#oidc-authentication) - **Login Problems** - See [FAQ](FAQ.md#authentication) - **Security Questions** - See [SECURITY.md](https://github.com/homelabforge/mygarage/blob/main/SECURITY.md) - **Community** - [GitHub Discussions](https://github.com/homelabforge/mygarage/discussions) --- **Next**: Configure [Database](Database-Configuration) or set up [Reverse Proxy](Reverse-Proxy).