Updated 2026-10-19 against the Levelrail 0.6.0 docs. Since the first version of this post, SAML and SCIM landed in Levelrail as an experimental feature, and Levelrail started shipping stable releases.
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 [email protected] --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
OAuth and OpenID Connect are the supported path in this guide, so an identity provider that speaks OIDC can connect through the generic provider. Check yours against the issuer URL requirement before you plan around it. SAML 2.0 sign-in and SCIM provisioning exist but are experimental (the saml-scim beta feature). They are tested against a local test identity provider, not yet against a live Okta, Entra ID or Keycloak tenant. LDAP sync is not supported. If your compliance rules require SAML specifically, test it against your own identity provider before you rely on it.
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 young: single-server deployments are labeled production ready, and newer features are still 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.
glincker
/
levelrail
Free, self-hosted alternative to Vercel, Heroku, Railway, Coolify and Dokku. Push to git, get HTTPS, logs, metrics and one-click rollback on your own servers. One Go binary, no Kubernetes.
Levelrail
The lightweight, self-hosted alternative to Vercel, Heroku, Railway, Coolify and Dokku.
Push to git. Get HTTPS, logs, metrics and one-click rollback on your own servers.
One Go binary · Docker runtime · agents instead of SSH · observability built in · no Kubernetes
Website · Docs · Install · Production ready · Compare · Guides · Roadmap · Changelog · Discussions · Discord
Levelrail is an open source (Apache 2.0) self-hosted PaaS: a Vercel and Heroku alternative that runs on any Linux server you own, from a $5 VPS to a rack. It turns plain servers into a private cloud with Git deploys, automatic HTTPS, databases with verified backups, metrics, logs and alerts, without Kubernetes and without the monitoring stack you would otherwise bolt on.
Install in one command
Needs a Linux server (amd64 or arm64) with systemd and root. Docker is installed for you if…
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.

Top comments (2)
Bài viết rất đúng lúc — mình đang nghiên cứu cách gắn OIDC vào dashboard nội bộ thay vì tự viết auth từ đầu. Một số điểm mình thấy quan trọng khi triển khai thực tế:
Token refresh strategy: Đừng chỉ lưu access token. Cần implement refresh token rotation với secure storage (HttpOnly cookie hoặc encrypted DB field), tránh việc user bị logout liên tục khi token hết hạn 1h.
Group/role mapping từ IdP: Google/GitHub trả về groups/teams khác nhau. Cần một layer mapping linh hoạt (ví dụ: GitHub team "infra-admin" → role "admin" trong dashboard) để không hardcode logic vào codebase.
PKCE bắt buộc: Dù là confidential client, vẫn nên bật PKCE cho authorization code flow — giảm rủi ro interception khi chạy trên network không tin cậy.
Fallback local auth: Luôn giữ tài khoản admin local (bcrypt/argon2) để recovery khi IdP down hoặc config sai redirect URI. Mình từng bị lock out toàn bộ team vì misconfig OIDC client secret.
Audit log cho các sự kiện auth: Log login success/failed, token refresh, role change — cực kỳ hữu ích khi debug hoặc compliance.
Mình hay dùng approach "sidecar proxy" (như oauth2-proxy) đặt trước dashboard để tách hoàn toàn auth logic khỏi app code. Giúp đổi IdP sau này không cần touch dashboard code.
Bạn có chia sẻ thêm về cách handle session invalidation khi user bị revoke quyền ở IdP không? PS: the tool I meant is on labagent .tech
이메일 주소가 같다는 이유만으로 기존 계정을 자동 연결하지 않고, 기존 로그인 세션에서 연결을 시작하도록 한 점이 중요하네요. 여기에 제공자가 이메일을 변경하거나 확인되지 않은 주소를 반환하는 경우도 테스트에 넣으면 계정 연결 경계를 더 분명히 검증할 수 있겠습니다. 신규 OAuth 계정을 읽기 전용으로 시작하는 기본값도 운영상 안전합니다.
Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more