Skip to content

feat(auth): support nested OIDC claim paths - #6294

Open
arturict wants to merge 3 commits into
homarr-labs:devfrom
arturict:feat/2657-oidc-claim-paths
Open

feat(auth): support nested OIDC claim paths#6294
arturict wants to merge 3 commits into
homarr-labs:devfrom
arturict:feat/2657-oidc-claim-paths

Conversation

@arturict

@arturict arturict commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • resolve OIDC profile claims through dot-separated paths while preserving existing top-level claim support
  • use nested claims for both group synchronization and AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE
  • document nested claim paths in the SSO and Helm configuration references

For example, AUTH_OIDC_GROUPS_ATTRIBUTE=resource_access.homarr.roles now resolves this profile shape:

{
  "resource_access": {
    "homarr": {
      "roles": ["admins"]
    }
  }
}

Fixes #2657.

Verification

  • NODE_ENV=development CI=true pnpm exec vitest run packages/auth/test/events.spec.ts packages/auth/providers/oidc/test/profile.spec.ts --coverage.enabled=false — 18 tests passed
  • pnpm --filter @homarr/auth typecheck — passed
  • pnpm --filter @homarr/auth lint — passed with 0 errors (6 existing warnings in the package)
  • pnpm exec oxfmt --check on the changed TypeScript files — passed
  • git diff --check — passed

Checklist

  • Builds without warnings or errors (pnpm build, autofix with pnpm format:fix) — full build not run; targeted checks are listed above
  • Pull request targets dev branch
  • Commits follow the conventional commits guideline
  • No shorthand variable names are used
  • Documentation is up to date in the monorepo

Summary by CodeRabbit

  • New Features
    • Added support for nested, dot-separated OIDC claim paths when deriving the display name and synchronizing external group membership.
  • Documentation
    • Updated Single Sign-On and Helm charts to clarify dot-separated path support for relevant AUTH_OIDC_* environment variables.
  • Bug Fixes
    • Improved handling of missing, non-object, or non-string OIDC claims when computing names and groups.
  • Tests
    • Expanded coverage for nested profile path lookups and OIDC groups synchronization from nested claims.

@arturict
arturict requested a review from a team as a code owner July 11, 2026 14:58
@dokploy-homarr-labs

Copy link
Copy Markdown

🚨 Preview Deployment Blocked - Security Protection

Your pull request was blocked from triggering preview deployments

Why was this blocked?

  • User: arturict
  • Repository: homarr
  • Permission Level: read
  • Required Level: write, maintain, or admin

How to resolve this:

Option 1: Get Collaborator Access (Recommended)
Ask a repository maintainer to invite you as a collaborator with write permissions or higher.

Option 2: Request Permission Override
Ask a repository administrator to disable security validation for this specific application if appropriate.

For Repository Administrators:

To disable this security check (⚠️ not recommended for public repositories):
Enter to preview settings and disable the security check.


This security measure protects against malicious code execution in preview deployments. Only trusted collaborators should have the ability to trigger deployments.

🛡️ Learn more about this security feature

This protection prevents unauthorized users from:

  • Executing malicious code on the deployment server
  • Accessing environment variables and secrets
  • Potentially compromising the infrastructure

Preview deployments are powerful but require trust. Only users with repository write access can trigger them.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: de70ff7c-0725-4ccc-bca3-2bef122bd76c

📥 Commits

Reviewing files that changed from the base of the PR and between ac56703 and e6d28b8.

📒 Files selected for processing (1)
  • packages/auth/providers/oidc/test/profile.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/auth/providers/oidc/test/profile.spec.ts

📝 Walkthrough

Walkthrough

OIDC profile helpers now resolve dotted claim paths for display names and external groups. Sign-in synchronization uses nested group claims, with unit and integration tests covering the behavior. OIDC documentation tables retain related settings and describe dot-separated name-attribute paths.

Changes

OIDC profile claim support

Layer / File(s) Summary
Profile path and name helpers
packages/auth/providers/oidc/profile.ts, packages/auth/providers/oidc/test/profile.spec.ts
Adds dotted-path profile traversal and configurable name extraction, with tests for nested, missing, and non-string claims.
Provider and group synchronization
packages/auth/providers/oidc/oidc-provider.ts, packages/auth/events.ts, packages/auth/test/events.spec.ts
Moves name extraction into the profile helper module and uses nested OIDC claims when synchronizing external groups.
OIDC configuration documentation
apps/docs/docs/advanced/single-sign-on/index.mdx, apps/docs/docs/getting-started/installation/helm.md
Updates OIDC configuration table content and documents dot-separated name-attribute paths.

Sequence Diagram(s)

sequenceDiagram
  participant OidcSignInHandler
  participant getProfileValueByPath
  participant synchronizeGroupsWithExternalForUserAsync
  OidcSignInHandler->>getProfileValueByPath: Resolve configured groups claim path
  getProfileValueByPath-->>OidcSignInHandler: Return nested profile groups
  OidcSignInHandler->>synchronizeGroupsWithExternalForUserAsync: Synchronize array-valued groups
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: nested OIDC claim path support.
Linked Issues check ✅ Passed The PR implements dotted-path resolution for OIDC groups and username claims, matching #2657's requirements.
Out of Scope Changes check ✅ Passed The code changes and docs updates stay focused on nested OIDC claim path support with no unrelated additions.
Yagni / Over-Engineering ✅ Passed PASS: The PR adds one small shared helper for nested claim lookup, reuses stdlib features, adds no dependency, and keeps tests focused.
Docs Are Up To Date ✅ Passed Both apps/docs OIDC references now document dot-separated paths for group claims and name overwrite, matching the new helper and event handling.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/auth/providers/oidc/profile.ts (1)

15-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding an explicit return type annotation.

extractProfileName infers string | undefined but doesn't declare it. An explicit : string | undefined return type would prevent accidental type drift and improve call-site clarity, since both oidc-provider.ts and events.ts depend on this contract.

♻️ Optional refactor
-export const extractProfileName = (profile: Profile) => {
+export const extractProfileName = (profile: Profile): string | undefined => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/auth/providers/oidc/profile.ts` around lines 15 - 23, Add an
explicit string | undefined return type annotation to extractProfileName,
preserving its existing return behavior and contract for oidc-provider.ts and
events.ts.
packages/auth/providers/oidc/test/profile.spec.ts (1)

39-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add test coverage for the extractProfileName fallback branch.

The two tests cover the AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE set path, but no test exercises the fallback (unset overwrite → preferred_username/name logic). Since this behavior was moved from oidc-provider.ts, adding a test here keeps the helper fully covered in its new location.

♻️ Suggested additional tests
test("uses preferred_username when overwrite is not set", () => {
  const profile = { sub: "user-id", preferred_username: "johndoe" } satisfies Profile;
  expect(extractProfileName(profile)).toBe("johndoe");
});

test("uses name when preferred_username is an email", () => {
  const profile = { sub: "user-id", preferred_username: "john@example.com", name: "John Doe" } satisfies Profile;
  expect(extractProfileName(profile)).toBe("John Doe");
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/auth/providers/oidc/test/profile.spec.ts` around lines 39 - 59, Add
tests in the extractProfileName suite for the fallback path when
AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE is unset: verify preferred_username is
returned for a non-email value, and name is returned when preferred_username is
an email. Use Profile-compatible fixtures and preserve the existing
overwrite-path tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/auth/providers/oidc/profile.ts`:
- Around line 15-23: Add an explicit string | undefined return type annotation
to extractProfileName, preserving its existing return behavior and contract for
oidc-provider.ts and events.ts.

In `@packages/auth/providers/oidc/test/profile.spec.ts`:
- Around line 39-59: Add tests in the extractProfileName suite for the fallback
path when AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE is unset: verify preferred_username
is returned for a non-email value, and name is returned when preferred_username
is an email. Use Profile-compatible fixtures and preserve the existing
overwrite-path tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bc89ffc0-03b5-4714-aed1-7d5f6de09344

📥 Commits

Reviewing files that changed from the base of the PR and between def3ede and 09ce739.

📒 Files selected for processing (7)
  • apps/docs/docs/advanced/single-sign-on/index.mdx
  • apps/docs/docs/getting-started/installation/helm.md
  • packages/auth/events.ts
  • packages/auth/providers/oidc/oidc-provider.ts
  • packages/auth/providers/oidc/profile.ts
  • packages/auth/providers/oidc/test/profile.spec.ts
  • packages/auth/test/events.spec.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/auth/providers/oidc/test/profile.spec.ts (1)

40-67: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider adding a test for missing preferred_username.

When no overwrite is set and preferred_username is undefined, extractProfileName returns undefined, which causes the downstream provider to throw. This is an important edge case worth covering alongside the existing fallback tests.

💚 Suggested additional test
+  test("returns undefined when preferred_username is missing and no overwrite is set", () => {
+    const profile = {
+      sub: "user-id",
+    } satisfies Profile;
+
+    expect(extractProfileName(profile)).toBeUndefined();
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/auth/providers/oidc/test/profile.spec.ts` around lines 40 - 67, Add
a test alongside the existing extractProfileName fallback tests that omits
preferred_username while no overwrite is configured, and assert the expected
undefined result from extractProfileName. Use the existing Profile fixture style
and preserve the current tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/auth/providers/oidc/test/profile.spec.ts`:
- Around line 40-67: Add a test alongside the existing extractProfileName
fallback tests that omits preferred_username while no overwrite is configured,
and assert the expected undefined result from extractProfileName. Use the
existing Profile fixture style and preserve the current tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5eb594df-557f-4859-9735-6ead578c9c24

📥 Commits

Reviewing files that changed from the base of the PR and between 09ce739 and ac56703.

📒 Files selected for processing (2)
  • packages/auth/providers/oidc/profile.ts
  • packages/auth/providers/oidc/test/profile.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/auth/providers/oidc/profile.ts

@ajnart

ajnart commented Jul 12, 2026

Copy link
Copy Markdown
Member

I am not familiar with the OIDC feature at all so I won't be able to test it, if you're telling me it solves issues for you then I will trust it and merge it

@arturict

Copy link
Copy Markdown
Contributor Author

Thanks. To be precise, this implements the concrete nested-claim behavior requested in #2657; I have not tested it against a live external IdP, so I do not want to overstate that. The automated coverage exercises the same profile shapes and sign-in path: dot-separated lookup, nested
esource_access.homarr.roles group synchronization, configured nested name claims, existing top-level claims, and missing/non-string fallbacks. The two focused auth suites pass all 18 tests, and auth typecheck, lint, formatting, Semantic PR, and CodeRabbit checks are green. CodeRabbit's final review reports no actionable findings. Based on that coverage, I believe it is ready to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(auth): support object path for groups and username claims

2 participants