-
Notifications
You must be signed in to change notification settings - Fork 407
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 AWS Cognito, which is not completely OIDC compliant #257
Conversation
Can this be conditioned on a specific issuer URL? E.g. so it only applies to AWS cognito? |
oidc.go
Outdated
@@ -192,6 +193,14 @@ type UserInfo struct { | |||
claims []byte | |||
} | |||
|
|||
// UserInfoRaw allows us to decode both compliant and non-compliant Cognito userInfo | |||
type UserInfoRaw struct { |
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 prefer to have this unexported so
type userInfoRaw struct
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.
good point. I made it unexported.
oidc.go
Outdated
@@ -365,6 +380,29 @@ type claimSource struct { | |||
AccessToken string `json:"access_token"` | |||
} | |||
|
|||
// Cognito reports email_verified as a string in userInfo, so try decoding as a bool or a string | |||
type StringAsBool bool |
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 here, this should be
type stringBool bool
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 made this change as well.
I conditioned the audience check on matching the cognito issuer in Verify. I didn't condition the UserInfo function to unmarshal into either UserInfo or userInfoRaw depending upon the issuer because it would require parsing the token to extract the issuer. But I'm happy to do that if you like. Or I could first unmarshal into UserInfo and if an error is returned, then parse the token, check the issuer, and if the issuer matches cognito then unmarshal into userInfoRaw (which I'd probably want to rename to userInfoCognito then). |
i'm happy to make any other improvements. |
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.
Also would you mind including a link to the AWS Cognito OpenID Connect docs as a comment somewhere in the code? After a bit of searching I can't find anything. |
I added tests, links to documentation, and squashed the commits. There's not a lot of documentation available though. I found these issues through trial and error. I want to make sure I'm thinking correctly. Auth0 sets aud on access tokens (as well as id tokens) and I've been passing Auth0 access tokens into Verify and verifying the audience. Cognito sets client_id on access tokens but not aud, which is why I added the client_id check for Cognito. (The client_id on a Cognito access token has the same value as aud.) But maybe instead of trying to verify the client ID for an access token I should set the SkipClientIDCheck flag in my code to skip client ID verification when I pass an access token to Verify, in which case I should remove the special-casing for Cognito client_id in Verify? I like the idea of having Verify verify the audience on access tokens, but is that incorrect? I'm not an expert in how to use OIDC. Also, I couldn't find the public/private keypair you used so I generated a new one and used that for the tests. |
Yes you can do the following without modifying go-oidc, you can even make this logic conditional on AWS Cognito: c := &oidc. Config{
SkipClientIDCheck: true,
}
v := provider.Verifier(c)
idToken, err := v.Verify(ctx, rawIDToken)
if err != nil {
// ...
}
var claims struct {
ClientID string `json:"client_id"`
}
if err := idToken.Claims(&claims); err != nil {
// ...
}
if claims.ClientID != clientID {
// return error
} Would it be better to change this PR to just do the user info fixes and document oidc.Config.ClientID better? Specifically that it verifies the "aud" claim and if you want to skip that validation just provide the oidc.Config.SkipClientIDCheck option? As a library maintainer I'd obvious prefer that since I don't have to deal with providers that go off spec :) |
In hindsight I agree that's the best approach. I reverted the changes to Verify, added a comment, and just left the changes to UserInfo. |
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.
Few comments, but lgtm otherwise
verify.go
Outdated
@@ -205,6 +205,10 @@ func parseClaim(raw []byte, name string, v interface{}) error { | |||
// | |||
// token, err := verifier.Verify(ctx, rawIDToken) | |||
// | |||
// Note that AWS Cognito access tokens include client_id instead of aud, so to verify 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.
Mind dropping this? We don't call out other providers like this. I can follow up with a doc change for the Config struct to specify that it's checking the "aud" claim for the client ID.
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.
Is there a reason this wasn't dropped?
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.
oversight. it's been removed now.
oidc.go
Outdated
@@ -192,6 +193,18 @@ type UserInfo struct { | |||
claims []byte | |||
} | |||
|
|||
// userInfoRaw allows us to decode both compliant and non-compliant Cognito userInfo | |||
// Cognito returns email_verified as a string instead of as a bool. | |||
// This is not documented by AWS as far as I can tell, but see for example |
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.
nit: Comments generally aren't in the first person.
Maybe just a comment above EmailVerified
type userInfoRaw struct {
Subject string `json:"sub"`
Profile string `json:"profile"`
Email string `json:"email"`
// Handle providers that return email_verified as a string.
// https://forums.aws.amazon.com/thread.jspa?messageID=949441󧳁
// https://discuss.elastic.co/t/openid-error-after-authenticating-against-aws-cognito/206018/11
EmailVerified stringAsBool `json:"email_verified"`
}
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 deleted the comments above and added the comments above EmailVerified.
oidc.go
Outdated
@@ -367,6 +385,29 @@ type claimSource struct { | |||
AccessToken string `json:"access_token"` | |||
} | |||
|
|||
// Cognito reports email_verified as a string in userInfo, so try decoding as a bool or a string |
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.
nit: drop the comment here.
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.
done
Is there anything else you need for this to get merged? |
… instead of a bool
I believe I have made all of the changes requested. |
Is everything ok with this PR? |
just following up on this PR. Is it ok to merge? |
Thank you! |
I've been using go-oidc and needed to support AWS Cognito, which varies from the OIDC spec in a couple of places. This PR makes go-oidc handle the inconsistencies. I'm happy to make changes or add tests. Just wanted to submit it for consideration.