Skip to content

Conversation

@ImpostorKeanu
Copy link
Contributor

  • have read the CONTRIBUTING.md file
  • raised a GitHub issue or discussed it on the projects chat beforehand
  • added unit tests
  • added integration tests
  • updated documentation if needed
  • updated CHANGELOG.md

When configured to use OIDC, email_verified=true is required to appear in ID token claims or user info before the email will be saved in a user's information. There are some scenarios where the email_verified claim cannot be sent/configured, such as when using Cloudflare One-time pin -- which doesn't support adding/modifying claims. This can make writing ACLs difficult or impossible.

This PR adds a configuration that relaxes the email_verified=true requirement by:

  • adding a configuration field: oidc.use_unverified_email
  • considering the setting when:
    • applying oidc.allowed_domains during authorization
    • user records are initialized via User.FromClaim

Related: #2655

@ImpostorKeanu
Copy link
Contributor Author

Just checking in on this. Please let me know if I've gone about the process wrong.

Think it's something we're interested in accepting?

Really appreciate everything.

@nblock
Copy link
Collaborator

nblock commented Nov 10, 2025

Really appreciate everything.

Thx for your PR! I'll do some testing once 0.27.1 is out and we'll come back to this afterwards.

@ImpostorKeanu
Copy link
Contributor Author

No rush at all! Thanks a ton!

Copy link
Collaborator

@nblock nblock left a comment

Choose a reason for hiding this comment

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

Thanks for your PR!

The following behavior makes authorization and profile update consistent:

For oidc.email_verified_required: true (default), require the e-mail to be verified to do any processing on it (email_verified: true). This gives as two cases:

  • email_verified: true:
    • accept if the email matches the oidc.allowed_users filter
    • accept if the domain matches the oidc.allowed_domains filter
    • update the email in the user's profile
  • email_verified: false (or not provided):
    • reject even if the email would match the oidc.allowed_users filter (breaking)
    • reject even if the domain would match the oidc.allowed_domains filter (breaking)
    • don't update the email in the user's profile

For oidc.email_verified_required: false, ignore the email_verified flag during processing. This results in:

  • accept if the email matches the oidc.allowed_users filter
  • accept if the domain matches the oidcs.allowed_groups filter
  • update the email in the user's profile

@ImpostorKeanu
Copy link
Contributor Author

So sorry for the delay on this! Got wrapped up in work and the holiday.

I'll try to knock it out later today.

Thanks!

@ImpostorKeanu
Copy link
Contributor Author

I fumbled the tests for a few minutes but everything should be in order now.

I'm not sure why the test workflow says it's waiting for a "status to be reported" for the tests. Weird.

If I've borked stuff, let me know and I'll fix it.

@nblock
Copy link
Collaborator

nblock commented Dec 10, 2025

Thanks for following up and implementing most of #2860 (review). It seems that the following cases are not implemented?

  • email_verified_required: true, email_verified: false (or not provided):
    • reject even if the email would match the oidc.allowed_users filter (breaking)
    • reject even if the domain would match the oidc.allowed_domains filter (breaking)
    • don't update the email in the user's profile

I tested it both cases ( oidc.allowed_users, oidc.allowed_domains) and noticed that a user with an unverified email address was able to authenticate.

Could you have a look at this?

@ImpostorKeanu
Copy link
Contributor Author

Thanks for following up and implementing most of #2860 (review). It seems that the following cases are not implemented?

* `email_verified_required: true`, `email_verified: false` (or not provided):
  
  * reject even if the email would match the `oidc.allowed_users` filter (breaking)
  * reject even if the domain would match the `oidc.allowed_domains` filter (breaking)
  * don't update the email in the user's profile

I tested it both cases ( oidc.allowed_users, oidc.allowed_domains) and noticed that a user with an unverified email address was able to authenticate.

Could you have a look at this?

Checking into it but I have a question:

As of now, OIDC authorization checks are AND'd. Shouldn't they be OR'd?

Example:

If both AllowedDomains and AllowedGroups are configured and claims.Email doesn't have an allowed domain, AllowedGroups isn't evaluated.

@ImpostorKeanu
Copy link
Contributor Author

I refactored to OR authorization checks, cleaned stuff up, and added standalone tests for OIDC authorization. Some of the decisions are questionable. All tests are succeeding on my side. Still waiting to see if GitHub actions wreck me.

I'm open to changes/rejection. Feel free to share your thoughts, and sorry if I'm making this more complicated than it has to be.

@nblock
Copy link
Collaborator

nblock commented Dec 11, 2025

As of now, OIDC authorization checks are AND'd. Shouldn't they be OR'd?

This could be discussed as a possible follow-up to this PR, but let's don't change the current behavior in this PR and keep it focused (docs).

(Feel free to rebase and squash as single commit.)

@ImpostorKeanu
Copy link
Contributor Author

ImpostorKeanu commented Dec 12, 2025

As of now, OIDC authorization checks are AND'd. Shouldn't they be OR'd?

This could be discussed as a possible follow-up to this PR, but let's don't change the current behavior in this PR and keep it focused (docs).

(Feel free to rebase and squash as single commit.)

Ok, I reset to an earlier commit and saved those changes to another branch. I'll open another PR.

Latest push still moves authorization to a distinct function for testing while preserving the authorization flow.

nblock
nblock previously approved these changes Dec 16, 2025
@nblock nblock dismissed their stale review December 16, 2025 09:56

need to test it

@nblock
Copy link
Collaborator

nblock commented Dec 16, 2025

Thx, will have a look soonish

@kradalby kradalby added this to the v0.28.0 milestone Dec 17, 2025
Comment on lines 509 to 478
trustEmail := !cfg.EmailVerifiedRequired || bool(claims.EmailVerified)
hasEmailTests := len(cfg.AllowedDomains) > 0 || len(cfg.AllowedUsers) > 0
if !trustEmail && hasEmailTests {
return errOIDCUnverifiedEmail
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

An "internal server error" is raised when:

  • oidc.allowed_users is configured
  • oidc.email_verified_enabled is enabled
  • A user with an unverified e-mail address tries to authenticate.

Logs: ERR http internal server error error="authenticated principal has an unverified email" code=500

Browser: internal server error

This should raise a http/401 instead, similar to:

return NewHTTPError(http.StatusUnauthorized, "unauthorised domain", errOIDCAllowedDomains)
return NewHTTPError(http.StatusUnauthorized, "unauthorised group", errOIDCAllowedGroups)
return NewHTTPError(http.StatusUnauthorized, "unauthorised user", errOIDCAllowedUsers)

@kradalby kradalby requested a review from Copilot December 17, 2025 08:07
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Collaborator

@nblock nblock left a comment

Choose a reason for hiding this comment

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

Thx, did a quick rundown with the following testcases, looks good:

  • evr unset: defaults to true?
  • evr=true, ev=true: ack + email in profile
  • evr=true, ev=false: ack + email not in profile
  • evr=true, ev=true, oidc.allowed_users
    • match+email in profile
    • no match: reject?
  • evr=true, ev=true, oidc.allowed_domains
    • match+email in profile
    • no match: reject?
  • evr=true, ev=false, oidc.allowed_users
    • always reject and never update profile?
  • evr=true, ev=false, oidc.allowed_domains
    • always reject and never update profile?
  • evr=false, oidc.allowed_users
    • accept if match + profile update
  • evr=false, oidc.allowed_domains
    • accept if match + profile update

@kradalby will have another look at the code.

Add test cases covering:
- unverified email with allowed user filter
- no filters configured with unverified email
Add test case that verifies a user in the second allowed group
should be authorized. This test currently fails due to a bug
where validateOIDCAllowedGroups returns error on first non-match.
Move error return outside the for loop so all allowed groups
are checked before returning unauthorized. Previously, it
would return error on first non-matching group instead of
checking if any subsequent group matches.
@kradalby kradalby force-pushed the use-unverified-email branch from beabaed to d95e291 Compare December 18, 2025 09:52
Signed-off-by: Kristoffer Dalby <[email protected]>
Signed-off-by: Kristoffer Dalby <[email protected]>
@kradalby kradalby enabled auto-merge (rebase) December 18, 2025 11:39
@kradalby kradalby disabled auto-merge December 18, 2025 11:40
@kradalby kradalby enabled auto-merge (squash) December 18, 2025 11:40
@kradalby kradalby merged commit 7be2091 into juanfont:main Dec 18, 2025
14 checks passed
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.

3 participants