Skip to content
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

Merged
merged 1 commit into from
Sep 8, 2020

Conversation

DallanQ
Copy link
Contributor

@DallanQ DallanQ commented Jul 15, 2020

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.

@ericchiang
Copy link
Collaborator

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 {
Copy link
Collaborator

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

Copy link
Contributor Author

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
Copy link
Collaborator

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

Copy link
Contributor Author

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.

@DallanQ
Copy link
Contributor Author

DallanQ commented Jul 16, 2020

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).

@DallanQ
Copy link
Contributor Author

DallanQ commented Jul 17, 2020

i'm happy to make any other improvements.

Copy link
Collaborator

@ericchiang ericchiang left a comment

Choose a reason for hiding this comment

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

Need to add test cases, and please squash your commits:

func TestVerify(t *testing.T) {

func TestUserInfoEndpoint(t *testing.T) {

Otherwise lgtm

@ericchiang
Copy link
Collaborator

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.

@DallanQ
Copy link
Contributor Author

DallanQ commented Jul 21, 2020

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.

@ericchiang
Copy link
Collaborator

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 :)

@DallanQ
Copy link
Contributor Author

DallanQ commented Jul 22, 2020

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.

Copy link
Collaborator

@ericchiang ericchiang left a 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
Copy link
Collaborator

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.

Copy link
Collaborator

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?

Copy link
Contributor Author

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
Copy link
Collaborator

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&#949441
	// https://discuss.elastic.co/t/openid-error-after-authenticating-against-aws-cognito/206018/11
	EmailVerified stringAsBool `json:"email_verified"`
}

Copy link
Contributor Author

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
Copy link
Collaborator

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@DallanQ
Copy link
Contributor Author

DallanQ commented Aug 12, 2020

Is there anything else you need for this to get merged?

@DallanQ
Copy link
Contributor Author

DallanQ commented Aug 23, 2020

I believe I have made all of the changes requested.

@DallanQ
Copy link
Contributor Author

DallanQ commented Aug 31, 2020

Is everything ok with this PR?

@DallanQ
Copy link
Contributor Author

DallanQ commented Sep 8, 2020

just following up on this PR. Is it ok to merge?

@ericchiang ericchiang merged commit 0a5cd33 into coreos:v2 Sep 8, 2020
@DallanQ
Copy link
Contributor Author

DallanQ commented Sep 8, 2020

Thank you!

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.

2 participants