Add Google, GitHub or OIDC Sign-In to Your Self-Hosted Dashboard, and Keep It Safe
Add Google, GitHub or OIDC Sign-In to Your Self-Hosted Dashboard, and Keep It Safe Single...

Add Google, GitHub or OIDC Sign-In to Your Self-Hosted Dashboard, and Keep It Safe
Single sign-on on a self-hosted tool is a small project with a long tail of mistakes. The login button is the easy part. The hard questions come after: who gets access when a stranger signs in, what stops an account takeover through a matching email, and how the CLI logs in without a browser.
This guide sets up OAuth and OpenID Connect sign-in on Levelrail, a self-hosted platform I am building, and works through each of those questions. It supports Google, GitHub, Microsoft and any generic OIDC provider. SAML and SCIM are not supported, so if your company mandates either, read the last section first.
TL;DR
| Step | What you do |
|---|---|
| 1 | Register an OAuth app at the provider |
| 2 | Enable the provider in Settings with its client ID and secret |
| 3 | Restrict who can sign up by email domain |
| 4 | Understand what a new user can do |
| 5 | Invite teammates and give them roles |
| 6 | Log in from the CLI with a device code |
Step 1: register an app at the provider
At Google, GitHub, Microsoft or your own identity provider, create an OAuth application. You will need to give it a redirect URI, which is the callback path on your dashboard:
/api/v1/auth/oauth/PROVIDER/callback
Combine that path with your dashboard's HTTPS address. Replace PROVIDER with google, github, microsoft or oidc. Use the HTTPS dashboard domain, not a raw IP and port, since most providers reject plain HTTP redirect URIs outside localhost.
If your dashboard still runs on HTTP, finish the domain setup first. The installer's first-run wizard walks through it, and once you save an HTTPS dashboard URL, the server refuses plain HTTP sign-in anyway.
Step 2: enable the provider
In the dashboard, open Settings, then OAuth, pick the provider, and paste in the client ID and client secret. For generic OIDC you also supply the issuer URL. The CLI does the same:
levelrail-cli settings oauth set google --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRET
levelrail-cli settings oauth list
Swap google for github, microsoft or oidc. Omit --client-secret on a later update and the stored secret stays. Changing OAuth settings needs the root ability. The secret is write-only. After you save it, the API tells you only that a secret exists. Microsoft uses the common multi-tenant Azure AD endpoint.
Checkpoint: log out, open the login page and confirm a button for your provider appears.
Step 3: restrict signups by email domain
Each provider has an allowed email domain setting. With it set, a new signup outside that domain gets refused. For a company tool, set this before you announce the login page to anyone.
levelrail-cli settings oauth set google --client-id YOUR_CLIENT_ID --allowed-email-domain yourcompany.com
Without it, anyone who can authenticate with the provider and reaches your login page can create an account. The account starts with minimal access, as the next section shows, but you still do not want strangers in your user list.
Step 4: what happens when someone signs in
The sign-in logic has four outcomes, and the choices behind them are deliberate.
| Situation | Result |
|---|---|
| The identity is already linked to a user | Signs in as that user |
| A brand-new email, domain allowed | Creates a new user with read-only access |
| A brand-new email, domain not allowed | Refused with a 403 |
| An email that belongs to a different account | Refused outright |
The second row is the safe default. A fresh OAuth signup gets the read ability only, never root, because the first user of a platform never arrives through OAuth. An admin can raise their abilities later.
The fourth row prevents a real attack. If the platform auto-linked any matching email, an anonymous sign-in could take over an unrelated account just by controlling an address that matches. Refusing keeps accounts separate. To attach a provider to your own account, start from inside a live session:
/api/v1/auth/oauth/PROVIDER/link/start
That requires an existing signed-in session, so only the account owner can add a provider.
Step 5: invite teammates
Sign-in alone does not give anyone useful access. Roles and policies do. You can invite someone by email with one of the three role presets (admin, operator or viewer), or with an explicit ability list through --abilities:
levelrail-cli invites create --email teammate@yourcompany.com --role operator
The invite response always includes the accept link, whether or not you configured SMTP. A control plane with no email setup still works by copying the link into a chat.
The privilege check matters here. A caller cannot invite someone with an ability the caller lacks. Invites last seven days by default. A non-root caller sees only the invites they made, and a root user sees all pending ones.
For anything narrower than the three role presets, attach an IAM policy. Policies use the AWS Allow and Deny shape, scoped to app:<name>, database:<name> or *. The access control article in this series covers writing them.
Step 6: keep the CLI working
Signing in through a browser is no use to a terminal, and a plain HTTP session cookie is a bad fit for a script. The device-code flow solves both:
levelrail-cli auth login --device
The CLI shows a code, you approve it in a browser session, and the CLI receives a token. Token management is session-only by design, so a bearer token can never mint or revoke another token. That is why tokens create and auth login ask for a username and password or a device approval, and never accept a token flag.
Add a second factor and passkeys
OAuth sign-in skips the local TOTP prompt, since your identity provider is responsible for that factor. Turn on two-factor at the provider. For local password accounts, TOTP and passkeys exist, and a passkey sign-in completes a session the same way OAuth does, with no extra TOTP prompt.
Audit who signed in and what they did
Every mutating or sensitive request lands in the audit log with the actor, the ability, the method, the path, the status and the surface that made the call. Filter by client kind, or by agent label for automation:
levelrail-cli audit-log --client-kind dashboard --method POST
Export it as CSV when someone asks for evidence.
What is missing
SAML single sign-on is not built, and neither is SCIM provisioning. OAuth and OIDC work, so an identity provider that speaks OpenID Connect can connect through the generic provider. Check yours against the issuer URL requirement before you plan around it. If your compliance rules require SAML specifically, Levelrail cannot serve you today.
The access control system is also young. It has an end-to-end test that drives a real session through the real HTTP API, but it has far less time in production than the identity layers of older tools. Levelrail is beta.
Which identity provider would you connect first?
Go deeper
- Identity and access: sign-in, roles and IAM policies
- Security notes and threat model
- The full comparison with Coolify, Dokploy, CapRover, Dokku and Kamal
- New to Levelrail? Start with the five minute install
Try it, and tell me what breaks
Levelrail is open source under Apache 2.0. It is young, so every bug report changes what gets built next.
If this post saved you time, a star on the repo helps other self-hosters find it. Bugs and feature requests go in the issue tracker.
GDS K S · thegdsks.com · building Glincker · follow on X @thegdsks
Single sign-on is mostly a decision about who gets in by default.