ory / hydra

The most scalable and customizable OpenID Certified™ OpenID Connect and OAuth Provider on the market. Become an OpenID Connect and OAuth2 Provider over night. Broad support for related RFCs. Written in Go, cloud native, headless, API-first. Available as a service on Ory Network and for self-hosters.
https://www.ory.sh/?utm_source=github&utm_medium=banner&utm_campaign=hydra
Apache License 2.0
15.6k stars 1.5k forks source link

consent: Add ability to specify Access Token Audience #883

Closed aeneasr closed 6 years ago

aeneasr commented 6 years ago

Currently, the audience of the ID Token is set explicitly in the oauth2 handler:

    // done
    response, err := h.OAuth2.NewAuthorizeResponse(ctx, authorizeRequest, &Session{
        DefaultSession: &openid.DefaultSession{
            Claims: &jwt.IDTokenClaims{
                Audience:    authorizeRequest.GetClient().GetID(),
                Subject:     session.ConsentRequest.Subject,
                Issuer:      h.IssuerURL,
                IssuedAt:    time.Now().UTC(),
                ExpiresAt:   time.Now().Add(h.IDTokenLifespan).UTC(),
                AuthTime:    session.AuthenticatedAt,
                RequestedAt: session.RequestedAt,
                Extra:       session.Session.IDToken,
            },
            // required for lookup on jwk endpoint
            Headers: &jwt.Headers{Extra: map[string]interface{}{"kid": h.IDTokenPublicKeyID}},
            Subject: session.ConsentRequest.Subject,
        },
        Extra: session.Session.AccessToken,
    })

and the audience of the access token is set by the extra session. Instead, the audience should be a first-class citizen of the consent flow/responses for both ID and access token.

aeneasr commented 6 years ago

Actually, the audience should probably be defined by the client making the request, not the consent endpoint.

bbulczynski commented 6 years ago

How would you suggest to do that? Maybe using claims and setting value?

aeneasr commented 6 years ago

Not sure yet, needs investigation

aeneasr commented 6 years ago

So the proper course of action, to the best of my knowledge, is as follows:

  1. We're enabling clients to define audiences which they are allowed to request.
  2. We're defining one or more audience matching strategies (e.g. exact, wildcard, ...)
  3. We're checking every implicit, code, hybrid, cc flow for a request parameter called audience
  4. If audience is set (either in query or body), we will split that parameter using - well this one is tricky - some character. This could be whitespace or something else. The problem is we're dealing with URLs so all allowed characters could be the URL and it's not really easy to just pick one delimiter. I think to keep with previous practices of scope delimiters, we should go with whitespaces (or url encoded +)
  5. We check if each of the audiences is also an intended/allowed audience of the client using the matching strategy.
  6. We expose GetRequestedAudience() []string and GrantAudience(audience string) in the requester interface so that the consent endpoint can set/override this value.
aeneasr commented 6 years ago

Here's what I intend to add to the docs regarding this:

Audience

There are two types of audience concept in the context of OAuth 2.0 and OpenID Connect:

  1. OAuth 2.0: Access and Refresh Tokens are "internal-facing". The aud claim of an OAuth 2.0 Access and Refresh token defines at which endpoints the token can be used.
  2. OpenID Connect: The ID Token is "external-facing". The aud claim of an OpenID Connect ID Token defines which clients should accept it.

While modifying the audience of an ID Token is not desirable, specifying the audience of an OAuth 2.0 Access Token is. ORY Hydra implements this feature, which is not defined as an IETF Standard but is considered good practice in certain environments.

To use the audience feature, you must specify the intended audiences in the OAuth 2.0 Client's metadata on a per-client basis:

{
    "client_id": "...",
    "audience": ["https://api.my-cloud.com/user", "https://some-tenant.my-cloud.com/"]
}

The audience is a list of case-sensitive URLs. URLs must not contain whitespaces.

OAuth 2.0 Authorization Code, Implicit, Hybrid Flows

When performing an OAuth 2.0 authorize code, implicit, or hybrid flow, you can request audiences at the /oauth2/auth endpoint https://my-hydra.com/oauth2/auth?client_id=...&scope=...&audience=https%3A%2F%2Fapi.my-cloud.com%2Fuser+https%3A%2F%2Fsome-tenant.my-cloud.com%2F which requests audiences https://api.my-cloud.com/user and https://some-tenant.my-cloud.com/.

The audience query parameter may contain multiple strings separated by a url-encoded space (+ or %20). The audience values themselves must also be url encoded. The values will be validated against the whitelisted audiences defined in the OAuth 2.0 Client:

The requested audience from the query parameter is then part of the login and consent request payload as field requested_access_token_audience. You can then alter the audience using grant_audience.access_token when accepting the consent request:

hydra.acceptConsentRequest(challenge, {
  // ORY Hydra checks if requested audiences are allowed by the client, so we can simply echo this.
  grant_audience: {
    access_token: response.requested_access_token_audience,
    // or, for example:
    // access_token: ["https://api.my-cloud/not-user"]
  },

  // ... remember: false
  // ...
})

When introspecting the OAuth 2.0 Access Token, the response payload will include the audience:

{
  "active": true,
  // ...
  "audience": ["https://api.my-cloud/user", "https://api.my-cloud/user/1234"]
}

OAuth 2.0 Client Credentials Grant

When performing the client credentials grant, the audience parameter from the POST body of the /oauth2/token is decoded and validated according to the same rules of the previous section, except for the login and consent part which does not exist for this flow.

Aggouri commented 6 years ago

While modifying the audience of an ID Token is not desirable, specifying the audience of an OAuth 2.0 Access Token is. ORY Hydra implements this feature, which is not defined as an IETF Standard but is considered good practice in certain environments.

To use the audience feature, you must specify the intended audiences in the OAuth 2.0 Client's metadata on a per-client basis:

While I don't necessarily have the best writing skills, I think this would be slightly more clear:

While modifying the audience of an ID Token is not desirable, specifying the audience of an OAuth 2.0 Access Token is. This is not defined as an IETF Standard but is considered good practice in certain environments.

For this reason, Hydra allows you to control the aud claim of the access token. To do so, you must specify the intended audiences in the OAuth 2.0 Client's metadata on a per-client basis:

bbulczynski commented 6 years ago
  1. How do you plan to alter pairwise subject functionality to cooperate with that?
  2. Do you mind interchangeability of id_token between clients?
  3. How about client deciding about which of his auds should be visible to other? I think aud content should be decided per client by user on consent page.
aeneasr commented 6 years ago

How do you plan to alter pairwise subject functionality to cooperate with that?

Not related IMHO

Do you mind interchangeability of id_token between clients?

Not sure what you mean by that?

How about client deciding about which of his auds should be visible to other? I think aud content should be decided per client by user on consent page.

Depends on your understanding of audience. It can be user granted or defined per client. The audience is not officially specified nor is how you authorize audience.

bbulczynski commented 6 years ago

ok, i should have order 2. before 1. to make it clear, so let me explain that...

Do you think that id token could be shared between clients from aud list? Otherwise bother putting list of aud into token, ok client can check if that is intended for him, but why put there other aud values? I could imagine situation where third parties share id token, that is signed and can be verified, and pass information about user among themselves that way.

The open spec of aud explains rest.

Thank you.

aeneasr commented 6 years ago

Do you think that id token could be shared between clients from aud list? Otherwise bother putting list of aud into token, ok client can check if that is intended for him, but why put there other aud values? I could imagine situation where third parties share id token, that is signed and can be verified, and pass information about user among themselves that way.

Personally, I think it could be shared. However, this will not be addressed as part of this PR. The access token audience is the real deal, requests/use cases for oidc audience have not come up yet.

aeneasr commented 6 years ago

Thank you @Aggouri for the correction, updated in the docs!

bbulczynski commented 6 years ago

OK, thanks for sharing.

ErwinSteffens commented 3 years ago

@aeneasr

We're defining one or more audience matching strategies (e.g. exact, wildcard, ...)

Was the wildcard part also implemented? Not finding anything in the documentation about this.