-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
support for id_token_hint #1125
base: master
Are you sure you want to change the base?
Conversation
6d13ae3
to
f3147c2
Compare
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.
Seems reasonable. What do we do if the key's been rotated? Do we really need to verify the signature?
server/oauth2.go
Outdated
@@ -20,6 +20,7 @@ import ( | |||
"time" | |||
|
|||
jose "gopkg.in/square/go-jose.v2" | |||
"gopkg.in/square/go-jose.v2/jwt" |
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 don't need to use the jwt package for this. The jose.ParseSigned works fine https://godoc.org/gopkg.in/square/go-jose.v2#ParseSigned
server/oauth2.go
Outdated
@@ -376,6 +377,8 @@ func (s *Server) parseAuthorizationRequest(r *http.Request) (req storage.AuthReq | |||
scopes := strings.Fields(q.Get("scope")) | |||
responseTypes := strings.Fields(q.Get("response_type")) | |||
|
|||
connectorID := s.connectorIDFromIDTokenHint(q.Get("id_token_hint")) |
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.
Shouldn't we error if the token hint is incorrect?
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.
OPTIONAL. ID Token previously issued by the Authorization Server being passed as a hint about the End-User's current or past authenticated session with the Client. If the End-User identified by the ID Token is logged in or is logged in by the request, then the Authorization Server returns a positive response; otherwise, it SHOULD return an error. When possible, an id_token_hint SHOULD be present when prompt=none is used and an invalid_request error MAY be returned if it is not; however, the server SHOULD respond successfully when possible, even if it is not present. The Authorization Server need not be listed as an audience of the ID Token when it is used as an id_token_hint value.
Hmm not sure if the spec is clear there. Anyways, I can make this error out if reading the connectorID from the token fails.
server/oauth2.go
Outdated
// figure out key used for signing this | ||
var usedKey string | ||
for _, hdr := range tok.Headers { | ||
if jose.SignatureAlgorithm(hdr.Algorithm) != jose.RS256 { |
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.
In some distant future, I'd like to not hard code jose.RS256 everywhere. Maybe we can take this from a centrally configured place?
return | ||
} | ||
if idTokenHint != "" { | ||
// redirect to auth URL with the hint, using default scopes | ||
scopes := []string{"openid", "profile", "email"} |
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.
Seems odd that these aren't taken from the first page.
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.
Yeah I was a bit lazy here. I'm going to try to fix this.
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.
It's actually not that simple -- we don't know much of the granted scopes when retrieving the id_token 🤔 Since this is the example app, I haven't spent too much time on implementing this.
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.
np. it's not critical.
@ericchiang Actually, when it comes to what On the issue, I'm torn. I've had another look at the rotation logic (please correct me if I'm wrong): keys get dropped when there's no possibility to have a non-expired ID token signed with that key around. This opens up the question of what could possibly go wrong? Basically, we're opening up a way for someone (Bob) to send someone else (Cathy) to a certain connector by tricking her into opening |
f3147c2
to
a2423e4
Compare
Ok, I've updated the branch. Remaining issues/questions:
|
This seems like the biggest outstanding concern.
We're not guaranteed that the UserID actually corresponds to the username that the user entered to login. In LDAP your username might be "jane" but your userID would be "cn=jane,dn=people,..." |
Alright, I'll take care of this 😄 |
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
This way, we automatically get nicer output that has the test case name prefixed: --- PASS: TestParseAuthorizationRequest (0.01s) --- PASS: TestParseAuthorizationRequest/normal_request (0.00s) --- PASS: TestParseAuthorizationRequest/POST_request (0.00s) --- PASS: TestParseAuthorizationRequest/invalid_client_id (0.00s) --- PASS: TestParseAuthorizationRequest/invalid_redirect_uri (0.00s) --- PASS: TestParseAuthorizationRequest/implicit_flow (0.00s) --- PASS: TestParseAuthorizationRequest/unsupported_response_type (0.00s) --- PASS: TestParseAuthorizationRequest/only_token_response_type (0.00s) Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
a2423e4
to
e8fe811
Compare
@ericchiang I'm afraid e8fe811 is a bit of a blunt solution -- but I haven't found a better one. I'd appreciate any input on this. Also, I'd like to add tests. Currently, it seems like it might make most sense to add some unit tests for |
Any progress on this? |
@deric I've afraid I've dropped the ball on this. I'll try to pick it up 😅 -- no promises that it'll happen this week, though. |
@deric if you'd like to take over, be my guest! |
The spec (see #990) is a bit vague on this. I suppose you could interpret it as follows:
id_token_hint
SHOULD be there whenprompt=none
. However, whenprompt=none
is passed, dex should error out when it cannot fulfill the request in a promptless way. Now, it's obvious that PasswordConnector can't doprompt=none
-- for others, however, it's less obvious. It's perhaps possible to "forward" the prompt=none to OIDC connectors, but for SAML AuthnRequests, I'm not sure if that's a thing.However, what this does is opportunistically select the login provider based on the
id_token_hint
. If this results in a prompt or not depends on the provider, and we don't promise anything. So, the case here isprompt=none
is not set,id_token_hint
is OPTIONAL. We make use of that optional hint.To test-drive this, checkout the branch and start the example-app against a dex instance with the static passwordDB and example-connector, and follow along:
After a Login using Example, there's a button "use ID token hint":
You'll be redirected to
/auth/local
, and it will log you in without any need for interaction, so you'll be redirected to the callback:If you start a new process, by logging in from http://127.0.0.1:5555, and select the email provider, clicking "login with id token hint" gets you back straight away to the password prompt:
Now, technically, we'd probably have enough information to pre-fill that username field.
However, there might also be ways to forward the "requested subject" to OIDC and SAML -- and I believe this is what we should do? The edge case I've got in mind is this:
While this isn't a security hazard, it's potentially a UX issue.
What do you think? I suppose there's two options here:
(3.) is too much for a single PR, and I cannot commit to getting this done in all connectors.
(2.) seems OK though, but I'd prefer to split this in two PRs.
Ok-ish way to proceed? If so, I'm going to add tests.
TODOs
go-jose.v2/jwt
🤔 ✅make revendor
, all good now.