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

Documentation for Authentication #99

Closed
savitmk opened this issue Jun 18, 2020 · 18 comments
Closed

Documentation for Authentication #99

savitmk opened this issue Jun 18, 2020 · 18 comments

Comments

@savitmk
Copy link

savitmk commented Jun 18, 2020

I am attempting to set up drf-spectacular and am struggling to configure authentication.

We have extended DRF's TokenAuthentication, and it is included in the authentication_classes list on the target APIView. We also have another custom authentication class, which does not match any AuthenticationExtensions.

If authentication_classes=[SubclassedTokenAuthentication] only (excluding our other authentication), then the "Authorize" button appears in Swagger. When we add the other authentication class, no "Authorize" button appears in Swagger.

Ideally, if any of the list of authentication_classes matches one of the AuthenticationExtensions, then that should be used in the resulting schema. Alternatively, being able to configure via the extend_schema(auth=?) kwarg would work - but I cannot find documentation for this argument.

Thanks for the great library!

@savitmk
Copy link
Author

savitmk commented Jun 18, 2020

Apologies - I had accidentally had the suggested ApiKeyAuth entry in SPECTACULAR_SETTINGS. In fact, in no case does Swagger display "Authorize".

@tfranzel
Copy link
Owner

Hi! that is weird. the Token is one of the extension that allows subclasses (as you can see in the code): https://github.com/tfranzel/drf-spectacular/blob/master/drf_spectacular/authentication.py#L27. that may be a bug.

As for you other auth class. you can easily write an extension. look at the customization documentation. take the other auth extensions as example. you can have the extension anywhere in your code. it autoregisters the moment the interpreter comes by.

the extend_schema(auth=) is kind of an loose end. it is more a less a manual intervention. forget about it for now.

try ./manage.py spectacular --file schema.yaml and look at the warnings. they indicate if the auth was discovered properly. let me know how it is going

@savitmk
Copy link
Author

savitmk commented Jun 19, 2020

@tfranzel thanks! yes, by subclassing I was able to resolve the issue. I don't know why the matches_subclass did not pick up my sub-classed DRF TokenAuthentication - potentially a bug.

I would propose adding an example to the documentation, inside the Specify authentication with OpenApiAuthenticationExtension section:

class MyAuthenticationScheme(OpenApiAuthenticationExtension):
    target_class = "my.authentication.MyAuthentication"  # full import path OR class ref
    name = "MyAuthentication"  # custom name for your auth scheme

    def get_security_definition(self, auto_schema):
        return {
            "type": "apiKey",
            "in": "header",
            "name": "Authorization",
            "description": "Value should be formatted: `Token <key>`"
        }

@tfranzel
Copy link
Owner

@savitmk it was indeed a bug... small typo 🤦

good idea with the example. i'll add it. thanks. please close if that solves it for you.

tfranzel added a commit that referenced this issue Jun 19, 2020
@savitmk savitmk closed this as completed Jun 22, 2020
@savitmk
Copy link
Author

savitmk commented Jun 22, 2020

thank you @tfranzel !

@Wissperwind
Copy link

Wissperwind commented Feb 15, 2021

Hey,
Ok, I see how I can create those classes and add my custom authentification class as String in target class.
But what exactly am I supposed to do with my created class?
Adding it as authentification class in settings.py replacing my custom class leeds to an error that some argument 'target' is missing.

@tfranzel
Copy link
Owner

The extension class need not be added anywhere. you use your custom auth class as normal. The extension class gets auto-loaded once the interpreter parses it. just put it somewhere the interpreter will come by.

this should be documented more prominently. there is a info box here though: https://drf-spectacular.readthedocs.io/en/latest/blueprints.html

does that make sense? i should write a FAQ entry for this

@Wissperwind
Copy link

Wissperwind commented Feb 15, 2021

Sorry, I haven't understood it yet.
My project:

  • Custom Authentification class with my special JWT process used in settings.py:
    'DEFAULT_PERMISSION_CLASSES': [
    'myProject.JwtAuth.JwtAuth'
    ]

/login has @permission_classes([AllowAny]) to allow post to it without authenification.

Problem:
/login has a locked lock symbol in the swagger Doku
I would prefer an open one

Problem 2:
My get Methods all have an open lock symbol, but they require all my custom authentification.
I would prefer a locked lock symbol.

So I write a class like this:

class MyAuthenticationScheme(OpenApiAuthenticationExtension):
    target_class = 'myProject.JwtAuth.JwtAuth'  # full import path OR class ref
    name = 'MyJWTAuthentication'  # name used in the schema

    def get_security_definition(self, auto_schema):
        return {
            'name': 'token',
        }

It is correct? I don't no exactly what to return.
And I imported that class in views.py.
That changed nothing in the swagger view.
Do I have to add somethin like this:
@extend_schema(auth='MyJWTAuthentication') ?
That causes errors by the way.

How can I achieve correct lock symbols in my swagger view, and how can I add to each view, that a token in the header is required?
Thanks for your help!

@revmischa
Copy link

/login has @permission_classes([AllowAny]) to allow post to it without authenification.
Problem:
/login has a locked lock symbol in the swagger Doku
I would prefer an open one

I also have a similar problem. I want API key auth on by default but I have a few monitoring endpoints that don't require API key auth. How do I disable the lock icon from appearing?

@api_view(["GET"])
@permission_classes([])
@extend_schema(auth=[{}])
def alive(request: Request) -> Response:
    return Response(status=status.HTTP_200_OK)

Still shows the lock icon.

@tfranzel
Copy link
Owner

tfranzel commented Feb 18, 2021

@revmischa @Wissperwind as far as i can tell, the outcome is correct and as expected. please tell me otherwise. here is the rationale behind it.

there is probably a confusion on what the lock symbol means. here is what i understand:

  • OPEN: auth required
  • CLOSED: either auth is optional or is already authenticated
  • no lock: no auth requred

these are the schema variations we may generate:

  1. security: basicAuth: [], {} --> auth optional but not needed.. closed lock (can be used)
  2. security: {} --> no auth needed, no lock
  3. no security section --> same as 2.
  4. security: basicAuth: [] --> lock open (auth required first)

@revmischa your problem is 1. to fix it you could remove your authentication_classes on those views. that should do it.

@Wissperwind: this has very little to do with the extension. the extension merely provides the security definition. the auth classes and permissions on the views determine security section in the schema and thus the shape of the lock.

you can test this in https://editor.swagger.io

openapi: 3.0.3
info:
  title: ''
  version: 0.0.0
paths:
  /doesitall/:
    post:
      operationId: customname_create
      description: this weird endpoint needs some explaining
      summary: short summary
      requestBody:
        content:
          application/json:
            schema:
              type: string
        required: true
      security:
      - basicAuth: []
      - {}
      responses:
        '201':
          description: ''
components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic

@revmischa
Copy link

@tfranzel I am using:

"DEFAULT_AUTHENTICATION_CLASSES": (),
    "DEFAULT_PERMISSION_CLASSES": [
        # API key required to access default endpoints
        "myapp.permissions.v1.api_key.HasClientAPIKey",
    ],

There are no authentication_classes set. I'm using djangorestframework-api-key. I want a lock by default on all endpoints except for "alive" endpoints.
I would like to be able to set security: [] for those views but I do not see how to do it with @extend_schema.

@Wissperwind
Copy link

Ok, I now better understand the meaning of the lock symbols. Thanks!
But I still don't understand what code like this does:

class MyAuthenticationScheme(OpenApiAuthenticationExtension):
    target_class = 'myProject.JwtAuth.JwtAuth'  # full import path OR class ref
    name = 'MyJWTAuthentication'  # name used in the schema

    def get_security_definition(self, auto_schema):
        return {
            'name': 'token',
        }

Code like this was recommended for custom Authentification methods. But what is the effect?

@tfranzel
Copy link
Owner

@Wissperwind this tells spectacular how your custom auth works and what is expected... header, cookie, etc. if a view has this auth, spectacular wires this with the specification section (at the end) and all is good. If you have a custom auth scheme, spectacular cannot know how it works without that additional info.

@revmischa how does your security section look like now with these settings? security: [] and security: [{}] should have the same behavior (no lock). have you been following this advice? then i get the problem.

@revmischa
Copy link

I am following that advice, it works nicely. The problem is that I want to set security to [] for individual endpoints and I'm at a loss of how to do that.

@tfranzel
Copy link
Owner

ok, just as i suspected. now i understand your problem. let me think about it for a moment. it is a use case i have not anticipated because above advice in itself is already a "hack" and we would need to find a solution that works for all the cases.

@ObserverOfTime
Copy link

I have the same use case and developed this hack for it:

empty = type('empty', (list,), {'__bool__': lambda _: True})()

empty == [] # True
bool(empty) # True

You can use it like so:

@extend_schema(auth=empty)
def alive(request: Request) -> Response:
    return Response(status=status.HTTP_200_OK)

Ideally though, auth should accept DRF's empty instead—like request & response already do—which can be converted to an empty list in get_auth. I can submit a PR for that if it's an acceptable solution.

@tfranzel
Copy link
Owner

tfranzel commented Dec 8, 2021

sry, I kind of forgot about this.

@ObserverOfTime the empty solution is a nice idea, but I don't think we even need it here. We could do it similar to tags. For request/responses the None is a valid choice, which we do not nessecarily need here. To convey that information with an empty list should suffice (effectively distinguishing between None and []).

auth=None -> default behavior
auth=['foo'] -> explicit auth method override
auth=[] -> explicit empty list / remove auth methods override

the last case is new and removes the security section, which should do the the trick as these 3 variations are functionally identical: security: [{}] == security: [] == no security section.

I already prepared a commit. no need for a PR. just let me know if that makes sense to you.

SIDENOTE: with the current version you could do auth=[{}], which tricks the condition to being truthy and thus clear the methods. mypy will complain about the type but it should produce the expected outcome until the fix is released.

@ObserverOfTime
Copy link

Makes sense to me.

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

No branches or pull requests

5 participants