Add Authentik OIDC login as an additional sign-in option
CI / web (push) Successful in 19s
CI / api (push) Successful in 28s

Local auth stays the primary/always-available login (don't want to
lock out the saved admin password) — OIDC is additive, shown as a
second button when OIDC_ENABLED=true. Uses openid-client v6 with PKCE.

Authentik-side provider was set up via an authentik blueprint (its own
declarative automation, see docs/oidc-setup.md) rather than touching
any existing admin credentials.

Closes #12.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-12 20:59:25 -06:00
parent 9891751d37
commit a431b87f1f
12 changed files with 268 additions and 8 deletions
+4
View File
@@ -24,6 +24,10 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
return res.json() as Promise<T>;
}
export function getAuthConfig(): Promise<{ oidcEnabled: boolean }> {
return request("/api/auth/config");
}
export function login(username: string, password: string): Promise<{ username: string }> {
return request("/api/auth/login", { method: "POST", body: JSON.stringify({ username, password }) });
}
+21
View File
@@ -42,6 +42,27 @@ body {
cursor: pointer;
}
.login-divider {
text-align: center;
color: #8b949e;
font-size: 0.8rem;
}
.oidc-button {
display: block;
text-align: center;
padding: 0.5rem;
border-radius: 4px;
border: 1px solid #30363d;
background: #0d1117;
color: #e6edf3;
text-decoration: none;
}
.oidc-button:hover {
border-color: #58a6ff;
}
.error {
color: #e5484d;
}
+17 -2
View File
@@ -1,11 +1,18 @@
import { useState } from "react";
import { login } from "../api";
import { useEffect, useState } from "react";
import { login, getAuthConfig } from "../api";
export function Login({ onLoggedIn }: { onLoggedIn: () => void }) {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false);
const [oidcEnabled, setOidcEnabled] = useState(false);
useEffect(() => {
getAuthConfig()
.then((c) => setOidcEnabled(c.oidcEnabled))
.catch(() => setOidcEnabled(false));
}, []);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
@@ -42,6 +49,14 @@ export function Login({ onLoggedIn }: { onLoggedIn: () => void }) {
<button type="submit" disabled={submitting}>
{submitting ? "Signing in…" : "Sign in"}
</button>
{oidcEnabled && (
<>
<div className="login-divider">or</div>
<a className="oidc-button" href="/api/auth/oidc/login">
Sign in with Authentik
</a>
</>
)}
</form>
</div>
);