Skip to content

Conversation

lheckemann
Copy link
Member

No description provided.

Copy link
Member

@dasJ dasJ left a comment

Choose a reason for hiding this comment

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

We actually have a patch for our idam that is less generic than yours (which is why it's not upstreamed). The comments probably reflect that ;)
I do like your approach which using proper libraries a lot! Also, how testable is this?


my $after = "/" . $c->req->params->{after};

$c->res->cookies->{'after_oidc'} = {
Copy link
Member

Choose a reason for hiding this comment

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

I saw this in GitHub as well and I really don't know why we would do this. We already have a session so why not do:

$c->session->{oidc_after} = $after;

You already do this for the state (which is what GitHub is not doing right now afaik 👀)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this is just taken directly from the other auth methods. We did discuss that it should also probably be a single session variable regardless of the auth method. I'd prefer to fix this later though.

$c->session->{oidc_state} = $state;
my $redirect_url = $oidc_client->uri_to_redirect(
redirect_uri => $c->uri_for('/oidc-login'),
scope => q{openid},
Copy link
Member

Choose a reason for hiding this comment

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

Would be nice to have this configurable

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure that's desirable: we care about the name, email and sub fields which are standard in openid, and I would very much like to avoid going further than that and requiring mapping logic.


# Be paranoid about the email address format, since we do use it
# in URLs.
die "Illegal email address.\n" unless $email =~ /^[a-zA-Z0-9\.\-\_]+@[a-zA-Z0-9\.\-\_]+$/;
Copy link
Member

Choose a reason for hiding this comment

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

Unrelated: this is the absolute worst because it excludes plus-emails

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes.

error($c, "Email address must be verified.", 401) unless $claims->{email_verified};

doEmailLogin($self, $c, "oidc", $claims->{email}, $claims->{name} // undef, sprintf("oidc:$claims->{sub}"));

Copy link
Member

Choose a reason for hiding this comment

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

I think it'd be nice to have some role mapping as well because otherwise everyone has no permissions. We use:

+    my $role_mapping = $c->config->{oidc}->{role_mapping} or ();
+    # Give out roles
+    $c->user->userroles->delete;
+    push(@{$user_data->{groups}}, ("default"));
+    foreach my $group (@{$user_data->{groups}}) {
+        if (defined($role_mapping->{$group})) {
+            my $roles = $role_mapping->{$group};
+            if (ref($roles) eq 'ARRAY') {
+                for my $mapped_role (@{$roles}) {
+                    $c->user->userroles->create({ role => $mapped_role });
+                }
+            } else {
+                $c->user->userroles->create({ role => $roles });
+            }
+        }
+    }

Copy link
Member Author

Choose a reason for hiding this comment

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

We discussed this at length (this was what took longest by far), and came to the conclusion that this is a real can of worms, because OIDC is about authentication and identity but not authorisation. For now, our recommended approach is to manage permissions manually.

sub oidc_login :Path('/oidc-login') Args(0) {
my ($self, $c) = @_;

error($c, "Logging in via OIDC is not enabled.", 404) unless $c->config->{enable_oidc_login};
Copy link
Member

Choose a reason for hiding this comment

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

Why not scope these? Something like $c>config->{oidc}->{enable}? Or even better $c->config->{oidc}->{client_id} which can be undef or defined and enable/disable oidc auth that way


doEmailLogin($self, $c, "oidc", $claims->{email}, $claims->{name} // undef, sprintf("oidc:$claims->{sub}"));

$c->res->redirect($c->uri_for($c->res->cookies->{'after_oidc'}));
Copy link
Member

Choose a reason for hiding this comment

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

Same issue as with the GitHub auth: Doesn't work. If you use the cookie (and not the session which I recommended in another comment), you need $c->req->cookies, not res

Copy link
Member Author

Choose a reason for hiding this comment

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

Ergh, you're right. I think it did work for some reason though.

<div class="dropdown-divider"></div>
[% END %]
[% IF c.config.enable_oidc_login %]
<a class="dropdown-item" href="/oidc-redirect?after=[% c.req.path %]">Sign in with OIDC</a>
Copy link
Member

Choose a reason for hiding this comment

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

Would also be nice to be able to change the name of the provider so it can be consistent in an organization.

$c->session->{access_token} = $token_response->access_token;
$c->session->{expires_at} = time() + $token_response->expires_in;
$c->session->{refresh_token} = $token_response->refresh_token;

Copy link
Member

@arianvp arianvp Sep 4, 2023

Choose a reason for hiding this comment

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

We really ought to check the id_token and its signature here and make sure that the id_token is bound to the audience (client_id).. And then save the sub and compare it to the sub received in userinfo.

Otherwise we are vulnerable against token substitution attacks as the access token is not audience-bound.

NOTE: Due to the possibility of token substitution attacks, the UserInfo Response is not guaranteed to be about the End-User identified by the sub (subject) element of the ID Token. The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token; if they do not match, the UserInfo Response values MUST NOT be used.

https://openid.net/specs/openid-connect-basic-1_0.html#UserInfo

We're not reusing the access_token long-term but still. we should use the id_token as the source of truth about the user identity. Not the userinfo response. the userinfo endpoint is there to augment the id_token given that you know and verified the sub. Not as a replacement for the id_token

@ners
Copy link
Member

ners commented Aug 17, 2025

I would like to host Hydra in an environment where not having custom SSO is a blocker, so I am quite keen on this change. What can we do to push it through? @lheckemann if you're not working on this anymore, can I take it over?

I've rebased against master here: https://github.com/ners/hydra/tree/oidc

@lheckemann
Copy link
Member Author

It's on my "one day" pile; if you have a bit more time pressure on it, you're very welcome to take over :)

@stepbrobd
Copy link
Member

i tried using @ners's rebased patch but couldn't get role mapping to work (with kanidm)... not quite sure if its my configuration issue or the support isn't there yet?

also, kanidm is complaining about PKCE:

No PKCE code challenge was provided with client in enforced PKCE mode. | event_tag_id: 12 | o2rs.name: "hydra"

so i had to set allowInsecureClientDisablePkce = true in oauth client configuration. since i really don't know much about security side of oidc, i'm not sure if this is a big risk or not

@stepbrobd
Copy link
Member

stepbrobd commented Sep 7, 2025

apparently i don't know how oidc role mapping work :-0
@ners resolved this for me at nixcon (nice)

example with kanidm:

services.hydra.extraConfig = ''
        enable_hydra_login = 0
        enable_oidc_login = 1
        oidc_client_id = "hydra"
        oidc_scope = "openid email profile groups"
        oidc_auth_uri = "https://kanidm.example.com/ui/oauth2"
        oidc_token_uri = "https://kanidm.example.com/oauth2/token"
        oidc_userinfo_uri = "https://kanidm.example.com/oauth2/openid/hydra/userinfo"
        include ${config.sops.secrets.hydra.path} # oidc_client_secret = ...

        <oidc_role_mapping>
          <my hydra admin role>@<kanidm tenant domain> = admin
          <my hydra admin role>@<kanidm tenant domain> = bump-to-front
          <my hydra user role>@<kanidm tenant domain> = cancel-build
          <my hydra user role>@<kanidm tenant domain> = eval-jobset
          <my hydra user role>@<kanidm tenant domain> = create-projects
          <my hydra user role>@<kanidm tenant domain> = restart-jobs
        </oidc_role_mapping>
'';

the only drawback of the current implementation is that we are missing PKCE support, otherwise patch lgtm (i can add docs if needed)

maybe this is the wrong place to approve pr since i'm testing against https://github.com/ners/hydra/tree/oidc, but i'll do it anyways

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.

5 participants