-
-
Notifications
You must be signed in to change notification settings - Fork 306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement generic OIDC-based authentication #1298
base: master
Are you sure you want to change the base?
Conversation
Co-Authored-By: Maximilian Bosch <[email protected]>
There was a problem hiding this 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'} = { |
There was a problem hiding this comment.
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 👀)
There was a problem hiding this comment.
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}, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
@@ -92,14 +104,16 @@ sub doLDAPLogin { | |||
} | |||
|
|||
sub doEmailLogin { | |||
my ($self, $c, $type, $email, $fullName) = @_; | |||
my ($self, $c, $type, $email, $fullName, $username) = @_; | |||
|
|||
die "No email address provided.\n" unless defined $email; | |||
|
|||
# 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\.\-\_]+$/; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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}")); | ||
|
There was a problem hiding this comment.
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 });
+ }
+ }
+ }
There was a problem hiding this comment.
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}; |
There was a problem hiding this comment.
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'})); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
@@ -143,6 +143,10 @@ | |||
<a class="dropdown-item" href="/github-redirect?after=[% c.req.path %]">Sign in with GitHub</a> | |||
<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> |
There was a problem hiding this comment.
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; | ||
|
There was a problem hiding this comment.
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
No description provided.