Skip to content

Retrieving the raw API key from within a view #98

@florimondmanca

Description

@florimondmanca

Is your feature request related to a problem? Please describe.
Currently, it's not obvious how we can access the raw API key from within a view that's protected behind a permission classes. (This may be useful in order to e.g. fetch resources associated to that key).

Users can access the request headers directly:

scheme, _, key = request.META["HTTP_AUTHORIZATION"].partition(" ")

but this essentially duplicates logic that's already defined on the HasAPIKey permission class applied to the view.

The only way to reuse that logic is to instantiate the permission class and call into .get_key()… But it doesn't feel very natural, does it?

class ProjectListView(APIView):
    permission_classes = [HasProjectAPIKey]

    def get(self, request: HttpRequest):
        key: str = HasProjectAPIKey().get_key(request)
        # ...

Describe the solution you'd like

Make .get_key() a class method, so that we can reuse it within a view a bit more naturally:

class ProjectListView(APIView):
    permission_classes = [HasProjectAPIKey]

    def get(self, request: HttpRequest):
        key: str = HasProjectAPIKey.get_key(request)
        # ...

Is this a breaking change? Luckily, it doesn't seem to be:

  • A class method is okay to use on an instance, so if people were already using the first way (HasProjectAPIKey().get_key()), they'll still be able to do it if we switch to a class method.
  • If people customized .get_key() (see Customization: Key parsing) and they defined it as an instance method, then surely they know how they're going to use it, and changing it on the base won't break anything for them anyway (even if they call super().get_key(), since that would also work within an instance method).

Describe alternatives you've considered

There are a couple of other ways we could have gone here, none of which are really okay:

  • Have BaseAPIKey set a magic .current_api_key (or similar) attribute on the view instance/class. We could make it work with type-checking, but it would lead to all sorts of weird edge cases and possible bugs related to shared view state. Also it probably wouldn't work with function-based views.
  • Introduce an APIKeyMiddleware that basically calls .get_key() and sets it on the request instance. This isn't great, because a) it would break type-checking (we're adding a new dynamic attribute on the request that isn't known to django-stubs), and b) it would expose the plaintext API key anywhere the request is accessible, which is a massive potential security hole if the developer isn't careful.
  • Introduce an APIKeyViewMixin that injects a .get_api_key(request) method on the view. That method would inspect the view's permission_classes. The problem is: there might be multiple API key permission classes set on the view, so which one would we use? "The one for which the API key is valid" won't work, because all permissions need to pass for a request to be authorized… So, nope.

The only sensible thing is to let the developer explicitly retrieve the raw API key from the request, using exact the permission class they want to use.

Additional context
Prompted by #93 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions