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

core: add api key flow #234

Closed
aeneasr opened this issue Aug 26, 2016 · 9 comments
Closed

core: add api key flow #234

aeneasr opened this issue Aug 26, 2016 · 9 comments
Labels
feat New feature or request. help wanted We are looking for help on this one. rfc A request for comments to discuss and share ideas.
Milestone

Comments

@aeneasr
Copy link
Member

aeneasr commented Aug 26, 2016

Hydra should support issuance of API tokens which are bound to a domain, a iOS namespace or an android namespace and targeted at services that require identification of public clients. One possibility would be to extend OAuth2 with a protocol (I could not find a spec for API tokens) or add an enpoint for it.

If OAuth2 is to be extended, the OAuth2 client will be the subject of the token. Upon request a namespace request var should be included containing a list of domans/ios/android namespaces.

API tokens are used for google maps and are used for quota checking and similar.

Here is an exemplary flow of creating such API Tokens on GCP.

1. Choose from options
ak1

2. Key created
ak2

3. Update key
ak3

4. Key overview
ak4

@aeneasr aeneasr added feat New feature or request. help wanted We are looking for help on this one. discuss labels Aug 26, 2016
@aeneasr aeneasr changed the title oauth2: add api token claim core: add api key flow Aug 28, 2016
@aeneasr
Copy link
Member Author

aeneasr commented Aug 28, 2016

I don't think that extending OAuth2 is smart. OAuth2 is for authorizing third party apps, web/mobile api keys are for authentication of public clients and quota checking and similar.

I think it should be possible to validate these tokens using the token valid warden endpoint. Tokens can be issued by any subject that is allowed to do so by policies. The endpoint would reside in:

POST /api-keys/web/
POST /api-keys/ios/
POST /api-keys/android/

The key will be matched against an url (web) or namespace (ios, android).

I am in the process of figuring out how to validate those tokens:

  1. warden: using existing infrastructure is good, but api keys are something very different from access tokens. api keys are used for quota checking (for example) whilst access tokens are used for higher priviledged functions. It would be intransparent for developers when an API key was used and when a access token was used. This could lead to serious vulnerabilities caused by tiny mistakes. probably the core reason why extending oauth2 doesn't make sense.
  2. new endpoint: adding another validation method sucks but it would be clear to the developer what's going on. it would require a lot of boilerplate too

@aeneasr
Copy link
Member Author

aeneasr commented Sep 23, 2016

Regarding #234 (comment) I am not 100% sure. One thing that comes with API tokens is that users are usually allowed to update allowed hosts and ios/andorid namespaces without having to change the API token everywhere.

In extension to above, API tokens are delegated authorization rights as well. They could, based on policies, allow user agents to do write requests as well (think anonymous comments). Using the existing OAuth2 infrastructure would additionally allow to leverage OAuth2 Access Token Validation as well as existing Access Token Issuance. By setting the TTL high (e.g. 50 years), they would basically never run out of validity and they could be revoked once #223 lands.

API Tokens could be, for example, exchanged by ID Tokens

POST /oauth2/token
Authorization: basic

grant_type=api_key
platform=web
namespaces=["foo", "bar"]
id_token=<id-token>

and modified through a new endpoint

POST /oauth2/api-keys/<key>

platform=ios
namespaces=["foo", "bar"]

One question to answer is if api keys can be issued with an id_token only, or if clients can issue those as well. I can currently not see any problems with allowing both, although restricting on id_token and opening up later would be the safer route.

Another thing that I have not solved yet is, the token's subject. There are two things to consider:

  • We need to be able to distinguish API Keys from regular Access Tokens. Otherwise, API Keys would receive the same rights that their owners have.
  • Additionally to checking if the API Key as such is allowed to perform an action, we also need to check if their owner is allowed to perform that action.

Validating the keys could be achieved using the LocalWarden:

The resulting access token could have a fixed subject (rn:hydra:oauth2:api-token) and carry the ID Token claims as part of it's own extra claims. This would allow for simple to set up policy checks and to identify the original user. The TokenAllowed method would then first check if the api token is allowed to do the stuff and then if the subject is too.

Alternatively, it could be achieved through policy conditions:

Doing both at the same time (checking what public API keys are allowed to do and checking what the user is allowed to do) could be done through policy conditions. This would however require the TokenAllowed method to append to the context if it encounters an API key. This does not feel very transparent.

@aeneasr
Copy link
Member Author

aeneasr commented Sep 29, 2016

I do not think that the following statement holds:

Another thing that I have not solved yet is, the token's subject. There are two things to consider:

  • We need to be able to distinguish API Keys from regular Access Tokens. Otherwise, API Keys would receive the same rights that their owners have.
  • Additionally to checking if the API Key as such is allowed to perform an action, we also need to check if their owner is allowed to perform that action.

The reason being that we want to limit the scope of an API Key, not distinguish in the resource server what to do if we encounter such a token. So instead of the things written above, API Keys should be issued to a limited set of scopes, which are always validated when looking up the token. For example:

We want users to be able to issue api keys in order to

  1. download images
  2. fetch some other data

Both of these endpoints have a certain scope:

  1. images.download
  2. data.read

Other endpoints, like uploading images would have different scopes like images.upload.

I think that the distinction here is: Policy lookups are used to check if a subject is allowed to do something. An API Key is issued on behalf of a subject, it is not a subject itself. Instead, the API Key carries a subset of to the subject's capabilities.

If this logic holds, it would make sense to have API Keys as an extension to OAuth2.

@aeneasr
Copy link
Member Author

aeneasr commented Oct 12, 2016

@aeneasr
Copy link
Member Author

aeneasr commented Oct 21, 2016

Some initial spec:

package oauth2

import (
    "net/http"

    "fmt"

    "github.com/ory-am/fosite"
    "github.com/pkg/errors"
    "golang.org/x/net/context"
    "time"
    "github.com/ory-am/fosite/compose"
    "github.com/ory-am/fosite/handler/oauth2"
)

// this feature is experimental, do not use it.

func OAuth2APIKeyGrantFactory(config *compose.Config, storage interface{}, strategy interface{}) interface{} {
    return &APIKeyGrantHandler{
        HandleHelper: &oauth2.HandleHelper{
            AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
            AccessTokenStorage:  storage.(oauth2.AccessTokenStorage),
            AccessTokenLifespan: time.Hour * 24 * 31 * 12 * 100,
        },
        ScopeStrategy: fosite.HierarchicScopeStrategy,
    }
}

type APIKeyGrantHandler struct {
    *oauth2.HandleHelper
    ScopeStrategy fosite.ScopeStrategy
}

func (c *APIKeyGrantHandler) HandleTokenEndpointRequest(_ context.Context, r *http.Request, request fosite.AccessRequester) error {
    if !request.GetGrantTypes().Exact("api_key") {
        return errors.Wrap(fosite.ErrUnknownRequest, "")
    }

    client := request.GetClient()
    for _, scope := range request.GetRequestedScopes() {
        if !c.ScopeStrategy(client.GetScopes(), scope) {
            return errors.Wrap(fosite.ErrInvalidScope, fmt.Sprintf("The client is not allowed to request scope %s", scope))
        }
    }

    // The client MUST authenticate with the authorization server as described in Section 3.2.1.
    // This requirement is already fulfilled because fosite requries all token requests to be authenticated as described
    // in https://tools.ietf.org/html/rfc6749#section-3.2.1
    if client.IsPublic() {
        return errors.Wrap(fosite.ErrInvalidGrant, "The client is public and thus not allowed to use grant type api_key")
    }
    // if the client is not public, he has already been authenticated by the access request handler.

    request.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().Add(c.AccessTokenLifespan))
    return nil
}

func (c *APIKeyGrantHandler) PopulateTokenEndpointResponse(ctx context.Context, r *http.Request, request fosite.AccessRequester, response fosite.AccessResponder) error {
    if !request.GetGrantTypes().Exact("api_key") {
        return errors.Wrap(fosite.ErrUnknownRequest, "")
    }

    if !request.GetClient().GetGrantTypes().Has("api_key") {
        return errors.Wrap(fosite.ErrInvalidGrant, "The client is not allowed to use grant type api_key")
    }

    return c.IssueAccessToken(ctx, r, request, response)
}

...still have to figure out how to check domains

@aeneasr aeneasr modified the milestones: milestone-omega: stable release, 0.6.0, unplanned Oct 21, 2016
@aeneasr
Copy link
Member Author

aeneasr commented Oct 26, 2016

@waynerobinson summed this up perfectly:

You just need a privileged client that can forge long-lived access tokens for specific scopes.

@aeneasr
Copy link
Member Author

aeneasr commented Dec 19, 2016

This can be solved much better with a proxy scenario. The proxy requests an oauth2 access and refresh token using authorize code and issues a handle, which is the API key. Then, when an API key is set in a request, it is replaced with the access token.

@pbarker
Copy link
Contributor

pbarker commented May 22, 2017

@arekkas I have an ask for this, are you still interested in implementing it?

@aeneasr
Copy link
Member Author

aeneasr commented May 22, 2017

Not really, API keys are actually for identification, not authorization so it's not really in the scope of hydra

@aeneasr aeneasr added the rfc A request for comments to discuss and share ideas. label Aug 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat New feature or request. help wanted We are looking for help on this one. rfc A request for comments to discuss and share ideas.
Projects
None yet
Development

No branches or pull requests

2 participants