feat: add OIDC subject claim fallbacks

This commit is contained in:
croatialu
2026-04-16 18:07:57 +08:00
parent addef55f30
commit d110dd2bcb
5 changed files with 45 additions and 0 deletions
+2
View File
@@ -98,6 +98,7 @@ const oidcConfig = type({
.optional(),
disable_api_key_login: "boolean = false",
scope: 'string = "openid email profile"',
subject_claims: "string[]?",
profile_picture_source: '"oidc" | "gravatar" = "oidc"',
extra_params: "Record<string, string>?",
@@ -120,6 +121,7 @@ const partialOidcConfig = type({
redirect_uri: "string.url?",
disable_api_key_login: "boolean?",
scope: "string?",
subject_claims: "string[]?",
extra_params: "Record<string, string>?",
profile_picture_source: '"oidc" | "gravatar"?',
+1
View File
@@ -115,6 +115,7 @@ const appLoadContext = {
: config.oidc.token_endpoint_auth_method,
usePkce: config.oidc.use_pkce,
scope: config.oidc.scope,
subjectClaims: config.oidc.subject_claims,
extraParams: config.oidc.extra_params,
profilePictureSource: config.oidc.profile_picture_source,
}),
+7
View File
@@ -217,6 +217,13 @@ integration:
# The scopes to request when authenticating users. The default is below.
# scope: "openid email profile"
# Optional fallback claims to use when your provider does not return a standard
# OIDC `sub` claim. Headplane always checks `sub` first, then each claim here
# in order. For Feishu/Lark, `["open_id", "email"]` is a reasonable fallback.
# subject_claims:
# - "open_id"
# - "email"
# Extra query parameters can be passed to the authorization endpoint
# by setting them here. This is useful for providers that require any kind
# of custom hinting.
+17
View File
@@ -70,6 +70,7 @@ oidc:
# token_endpoint: ""
# userinfo_endpoint: ""
# scope: "openid email profile"
# subject_claims: ["open_id", "email"]
# extra_params:
# foo: "bar"
```
@@ -78,6 +79,22 @@ Headplane automatically discovers OIDC endpoints from your issuer's
`/.well-known/openid-configuration`. If your IdP does not support discovery,
you'll need to set the endpoints manually.
### Non-standard Subject Claims
Some providers do not return the standard OIDC `sub` claim in the ID token.
Headplane always uses `sub` first, but you can configure fallback claims with
`oidc.subject_claims`.
For Feishu/Lark, the recommended configuration is:
```yaml
oidc:
subject_claims: ["open_id", "email"]
```
This keeps identity matching stable by preferring `open_id` and only falling
back to `email` if needed.
### PKCE
::: warning
+18
View File
@@ -112,6 +112,24 @@ describe("Configuration YAML file loading", () => {
expect(config.oidc?.enabled).toBe(false);
});
test("oidc.subject_claims can be configured from YAML", async () => {
const filePath = "/config/oidc-subject-claims.yaml";
writeYaml(filePath, {
headscale: { url: "http://localhost:8080" },
server: { cookie_secret: "thirtytwo-character-cookiesecret" },
oidc: {
issuer: "https://accounts.google.com",
client_id: "my-client-id",
client_secret: "my-client-secret",
headscale_api_key: "my-api-key",
subject_claims: ["open_id", "email"],
},
});
const config = await loadConfig(filePath);
expect(config.oidc?.subject_claims).toEqual(["open_id", "email"]);
});
test("partial oidc config with enabled field can be parsed", async () => {
const filePath = "/config/oidc-partial.yaml";
writeYaml(filePath, {