Repository navigation
Authentication
Configure user authentication and access control for MyGarage. Choose between no authentication, local JWT authentication, or OIDC/SSO integration.
MyGarage supports three authentication modes:
| Mode | Use Case | Security Level | Setup Complexity |
|---|---|---|---|
| None | Local, testing, single-user behind firewall | 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 |
- 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
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
✅ Appropriate:
- Local development (
localhostonly) - 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
After MyGarage is running, set the authentication mode in the web UI:
- Visit Settings → System
- In the Authentication Mode card, click the None tab, then Turn off sign-in, and confirm
- It takes effect at once
- 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
- ✅ 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)
Via Web UI:
- Visit Settings → System
- In the Authentication Mode card, click the Local tab, then Switch to local sign-in, and confirm
- It takes effect at once
- 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):
JWT_COOKIE_SECURE=auto # auto (default), true (HTTPS only), false (HTTP allowed)After enabling local auth:
- Visit the web interface
- On the sign-in page, click Register here
- Fill in:
- Username - Used to sign in
- Password
- Confirm Password
- Click "Create Account"
- First user automatically becomes admin
Registration is only open while there are no users. After that, an admin adds users (below).
8 to 100 characters, with at least one each of:
- Uppercase letter
- Lowercase letter
- Number
- Special character (
!@#$%^&*(),.?":{}|<>)
- Login as admin
- Go to Settings → System and click Manage Family in the Family Management card
- Turn on Multi-user mode (needed once, before adding users)
- Click "Add User"
- 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
- Click "Create"
- Share credentials with new user (securely!)
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
A vehicle can be shared with other users:
- Go to vehicle detail page
- Click "Share"
- Click "Share with another user", pick the user and Read or Write permission
- Click "Share"
See Family Multi-User for what each permission allows.
If user forgets password:
- Login as admin
- Go to Settings → System → Manage Family
- Click the key icon (Reset password) on the user's card
- Enter a new password and confirm it
- Click "Update"
This works for local accounts only, not OIDC users.
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.
- 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
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
Your OIDC provider must support:
- OpenID Connect (OIDC) protocol
- Authorization Code Flow
- ID tokens with
emailclaim
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:
MYGARAGE_TRUSTED_HOSTS=auth.example.com # comma-separated hostnames, IPs, or CIDRsThis 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.
Via Web UI:
- Visit Settings → System
- In the Authentication Mode card, click the OIDC tab, then Configure OIDC
- 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)
- Click "Test Connection" to check MyGarage can reach the provider
- Click Save
- Back on the Authentication Mode card, click Switch to SSO and confirm. It takes effect at once
- Visit the web interface → click "Sign in with "
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.
-
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
- Applications → Applications → Create; Name:
-
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
-
Provider Name:
-
Create Client in Keycloak:
- Go to Clients → Create Client
- Client ID:
mygarage - Client Protocol:
openid-connect - Access Type: Confidential
-
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!)
-
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
-
Provider Name:
-
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
-
Copy Client ID and Secret (shown after creation)
-
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
-
Provider Name:
-
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
-
Configure Application:
- Copy Application (client) ID
- Copy Directory (tenant) ID
- Go to Certificates & secrets → Create new client secret
- Copy secret value (shown only once!)
-
MyGarage UI Configuration (Settings → System → Configure OIDC):
-
Provider Name:
Azure AD -
Issuer URL:
https://login.microsoftonline.com/<tenant-id>/v2.0(replace<tenant-id>) - 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
-
Provider Name:
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
emailclaim is used - First user becomes admin
- Subsequent users are standard users
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.
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.
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:
- Go to Settings → System → Manage Family
- 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.
- 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:
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:
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.
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.
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.
Cause: Client secret incorrect or expired
Fix: Regenerate the secret in your provider, paste it into Settings → System → Configure OIDC, and click Save.
Cause: OIDC provider not returning email in ID token
Fix:
- Ensure
emailscope is requested - Configure OIDC provider to include
emailclaim in ID token - Some providers require explicit consent for email scope
- Visit Settings → System
- In the Authentication Mode card, click the Local tab, then Switch to local sign-in, and confirm. It takes effect at once
- On the sign-in page that opens, click Register here to create the first user
- Configure OIDC provider (create client - see examples above)
- Visit Settings → System
- In the Authentication Mode card, click the OIDC tab, then Configure OIDC
- Configure OIDC settings (provider, client ID, secret, etc.), click Test Connection, then Save
- Back on the Authentication Mode card, click Switch to SSO and confirm. It takes effect at once
- 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.
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.
- Default lifetime: 2 hours
- Sessions expire after this window; users must re-authenticate.
Users can manually logout:
- Click the gear icon in the top bar (Quick settings), or the logout button beside it on desktop
- "Logout"
- JWT cookie deleted
- Redirected to login page
If inactive for longer than JWT expiration:
- Session expires
- User redirected to login
- Must re-authenticate
- 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_KEYif you must share one secret across replicas.
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:
-
MYGARAGE_SETTINGS_KEY: the key itself. -
MYGARAGE_SETTINGS_KEY_FILE: the absolute path of a file holding it (for Docker secrets). A relative path is refused. -
/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:
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:
services:
mygarage:
environment:
MYGARAGE_SETTINGS_KEY_FILE: /run/secrets/mygarage_settings_key
secrets:
- mygarage_settings_key
secrets:
mygarage_settings_key:
file: ./secrets/mygarage_settings_keyCompose 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.
- 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). - Check that the log line says
unreadable: none. - 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.
- ✅ 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
- ✅ 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
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.
- 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
- 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
- OIDC Configuration Issues - See Troubleshooting Guide
- Login Problems - See FAQ
- Security Questions - See SECURITY.md
- Community - GitHub Discussions
Next: Configure Database or set up Reverse Proxy.