Dex: UserInfo endpoint

Created on 16 Mar 2016  路  26Comments  路  Source: dexidp/dex

I am currently trying to get dex working in a ruby environment. On the RP side I am currently working with omniauth-openid-connect, which has some issues of its own.

However: Working around some of those initial issues, I am now stuck with omniauth-openid-connect trying to get additional claims from the userinfo endpoint, which is not implemented by dex.

In reading the OIDC specification, I failed to recognize whether this is a mandatory endpoint or an optional endpoint, but since it appears right away in section 1.3 of the spec, I would _assume_ it is mandatory.

Are there any plans on implementing this endpoint?

If not, I _might_ start working on it, but that depends on a number of other factors and it would be my first larger golang contribution anywhere, not to mention that I have barely read enough of the specs...

areoidc-certification areoidc-spec kinenhancement

Most helpful comment

Will work on this in the upcoming weeks and submit a proposal for review

All 26 comments

It's recommended, not mandatory:

userinfo_endpoint
RECOMMENDED. URL of the OP's UserInfo Endpoint [OpenID.Core]. This URL MUST use the https scheme and MAY contain port, path, and query parameter components.

https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata

We currently add additional claims to the ID Token. For example:

{
  "aud": "example-app",
  "email": "[email protected]",
  "exp": 1458191394,
  "iat": 1458148194,
  "iss": "http://127.0.0.1:5556",
  "name": "Elroy Jonez",
  "sub": "elroy-id"
}

But yes, we should absolutely have a userinfo endpoint.

I was just thinking today how the UserInfo endpoint would be useful for some integrations on account.tectonic.com. Particularly for syncing user accounts to other services.

This would actually be really easy, since we currently use the ID Token as the access token[0]. We'd just decode that for the user.

[0] https://github.com/coreos/dex/blob/2d5fb0b47a3150d4fb2696f0736c12ee57d72ba7/server/http.go#L495-L500

Is this still planned? I would like to see this implemented before 2.0 gets final.

Is this still planned?

This isn't since all information we'd return with a userinfo endpoint is already returned in the id_token.

Also, right now nothing uses the access_token returned by dex v2 (it's actually just a random string), so implementing this requires some design around what an access_token is internally to dex.

I understand that their are clients that require this optional endpoint, but it's not a high priority for us.

Proposal:

Let's backpack off the proto types add for refresh tokens in https://github.com/coreos/dex/pull/757 to implement an access token. The token will look like:

message AccessToken {
    string subject = 1;
    string name = 2;
    string email = 3;
    bool email_verified = 4;

    // Time at which the access token expires, represented as a unix time.
    //
    // This is the same value as the id_token.
    int64 expiry_unix = 5;
}

The access token fields will be filled out by the Claims in the AuthCode struct[0].

The access token struct will be serialized, then signed as a JWS using the signing keys maintained by dex, and returned as the "access_token" payload. When a user hits the userinfo_endpoint the key ID of the JWS is used to lookup the internal key, which is verified then used to form the userinfo_endpoint response.

On a refresh, the new access_token is returned.

We also may consider using ECDSA keys if the signature is significantly shorter.

Because access_tokens are opaque to the end user, we can internalize this logic later if we need to if the access_token gets too big.

[0] https://godoc.org/github.com/coreos/dex/storage#AuthCode

Seems like a great approach. This is almost exactly what the OIDC spec recommends if your attempting to build a stateless OIDC provider too.

We also might consider encrypting the token instead of signing it. Just to make sure no one tries to rely on our access_token format.

A userinfo endpoint is required to pass the basic OpenID Connect Conformance Profile to achieve certification.

Certification issue: #42

I am trying to use dex with spinnaker and it needs the userInfoUri endpoint. Without this it's not possible to use dex with it.

See https://www.spinnaker.io/setup/security/authentication/oauth/#bring-your-own-provider

Same w/ anyone attempting to integrate with Centrify. Centrify doesn't include any of the expected data in that id_token JWT. The expectation is that you will call the userinfo endpoint to gain the detail you are looking for.

details here: https://docs.centrify.com/en/centrify/appref/index.html?version=1507675723#page/cloudhelp%2Fgen%2FSAML-gen-OpenID.2.html

The same issue happens when integrating with the GenericOAuthenticator module used by JupyterHub for OpenID Connect authentication.
It also expects the Userinfo endpoint on the provider to exist to fetch user details.

I am trying to use dex as an authentication provider with GitLab, and the omniauth-openid-connect gem which GitLab uses requires the userinfo endpoint as well.

I understand the philosophical standpoint that all information should be taken from the token, but at the same time, it seems like this stanza prevents using dex with a wide variety of software, which is a real shame.

Please consider the pragmatic solution of implementing the userinfo endpoint. Thank you!

Will work on this in the upcoming weeks and submit a proposal for review

That鈥檚 great news! Thank you.

@rithujohn191 I forked Dex and successfully implemented the User Info endpoint. I will share it as soon as I finish polishing it, but I will give some details on how it works.

Connectors of type oidc have a new configuration option userInfo where a user info endpoint for the OP can be provided. E.g.:

- type: oidc
  id: google
  name: Google
  config:
    issuer: https://accounts.google.com
    userInfo: https://www.googleapis.com/oauth2/v3/userinfo         <<<<<<
    clientID: $GOOGLE_CLIENT_ID
    clientSecret: $GOOGLE_CLIENT_SECRET
    redirectURI: http://127.0.0.1:5556/dex/callback
    hostedDomains:
    - $GOOGLE_HOSTED_DOMAIN

How it works technically:

  1. Once the user is authenticated with the OP, Dex receives an Access Token
  2. Dex stores this token and generates a random token that will be forwarded to the Relying Party (client). This random token is also used as the ID to identify the object that contains the real token in future requests.
  3. The random access token is then sent to the RP instead of the real access token

    • This forces the RP to use dex for all requests that require the access token

  4. The RP uses this token to make a User Info request to Dex
  5. Dex receives the random access token
  6. Dex uses the random access token to find the real Access Token stored in step 2
  7. Dex uses the real token to make a user info request to the configured userInfo endpoint of the OP
  8. Dex returns the result to the RP

Note: I did not consider token refresh.

This has only be tested with the generic OAuthenticator plugin of Jupyterhub which uses a basic OAuth2 flow.

@fjbsantiago Does your implementation also work with other connectors like LDAP?

@markuslindenberg Sorry, but I only implemented this for Open ID Connect connectors, unfortunately.

Just a heads up. Any dex user info implementation we add to dex will work with any backing connector (LDAP, SAML, GitHub, etc), not just with OpenID Connect.

I have another quick question @ericchiang.

Some OPs return different kinds of claims from the userinfo endpoint like, for example, birth date and gender.
Will these claims be lost if you implement the Access Token as per your proposal?

@NobodysNightmare I wrote an implementation for the UserInfo endpoint. It is currently pending as a pull request.

It would be nice if you could try it to see if it solves your issue with omniauth-openid-connect, unless you already found a workaround.

@fjbsantiago I have made a simple test of your code as openshift(kubernetes) auth provider

As i reviewed openshift openid plugin code
https://github.com/openshift/origin/blob/9a9a948e55f943e1aa2cff3965a7545f7809a380/pkg/auth/oauth/external/openid/openid.go#L187
and
http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse

I think the subject in AccessToken should be the same as in id_token
https://github.com/coreos/dex/pull/1133/files#diff-105fedf355a9fcad82982745cef24f7aR1055

@hardbone12 rolled my own, borrowed mostly from @fjbsantiago , fixed the subject issue

Hey Folks, any news on this?

Since I am not familiar with all flows and OIDC specifications, I have some dummy questions:

  • If I use a refresh token to request a new id_token, will the IdPs, who support offline_access, issue me new claims that reflect the changes in IdP (like changed groups, changed name etc)
  • If yes, then which url should I post request to? My guess is /token.
  • Which struct and format should I send to /token?
  • As I understand, the UserInfo endpoint discussed here is used for get the up-to-date info (new claims) from IdPs while not having refresh_token (like SAML) but having valid access_token ((in dex, i suppose access_token == id_token). Is it right?
  • Is there any plan to implement the UserInfo endpoint?

I've created a PR which adds userinfo_endpoint based on the current code and the previous PR from @jackielii https://github.com/dexidp/dex/pull/1454. Also available as a Docker image from https://hub.docker.com/r/mdbraber/dex

Was this page helpful?
0 / 5 - 0 ratings