oapi-codegen icon indicating copy to clipboard operation
oapi-codegen copied to clipboard

Support multiple authentication schemes for a single endpoint

Open alehed opened this issue 1 year ago • 6 comments

The OpenAPI spec allows specifying multiple security schemes per endpoint/api.

Examples taken from https://swagger.io/docs/specification/authentication/:

Example 1:

security:    # A OR B
  - A
  - B

Example 2:

security:    # A AND B
  - A
    B

Example 3:

security:    # (A AND B) OR (C AND D)
  - A
    B
  - C
    D

Currently the generated server code for example 1 and 2 are identical, which means they cannot be differentiated from any middlewares.

alehed avatar Jun 06 '24 09:06 alehed

I use oapi-codegen in this exact scenario, and this authentication logic doesn't need to go into the generated code, openapi3filter handles it just fine. It'll evaluate the boolean combination of auth schemes correctly, what you need to do is to provide a handler function to tell it whether each individual auth spec is correct.

What kind of generated code do you think would help here?

mromaszewicz avatar Jun 06 '24 11:06 mromaszewicz

Ideally the code generator would take care of this entirely, similarly like ogen does this with security handlers.

Alternatively, the generator could add some information to the request context about which authentication methods need to pass, such that a middleware can do the actual authentication. Right now we are using a middleware that guesses this information based on the scopes info in the request context.

I'm not super happy with using a separate tool that reads the api spec at runtime (this complicates deployment/testing).

alehed avatar Jun 06 '24 12:06 alehed

The generator doesn't have enough context to make an authorization decision for you entirely, this will always be a runtime check for your own code, with the aid of generated code or middleware. What I'm trying to say is that this can be done completely generically in middleware. This is how we do it for many API's that take multiple OIDC and BasicAuth providers.

At generation time, we don't know how you're going to validate each scheme, so that would have to be a callback for each kind, eg, A, B, C and D in your example, but then our generated code would entirely duplicate what the openapi3filter middleware does.

Let me poke around a bit and see how we can make this nicer today.

mromaszewicz avatar Jun 06 '24 13:06 mromaszewicz

I agree that it cannot be done without user-provided callbacks for A, B, C and D.

Right now we have a generic custom middleware which takes callbacks and does the authentication. But due to limited information it has, it can only check A OR B OR C OR D (or it could check the arguably less useful A AND B AND C AND D).

What our middleware does is basically:

for scopeKey, callback := range authenticators {
    if r.Context().Value(scopeKey) != nil {
        // call callback to check auth and break if successful
    }
}

if !authenticated {
    // send back 401
}

next.ServeHTTP(w, r)

So it checks for the scope keys in the context.

What I meant about the runtime thing is that to use openapi3filter you need to keep the yaml file around and have it available to the server at runtime (and we need to make sure that we use the same file for generation as well as validation, etc..).

alehed avatar Jun 06 '24 14:06 alehed

I just saw that you can use the EmbeddedSpec option to make this runtime loading of the spec potentially less painful.

alehed avatar Jun 06 '24 14:06 alehed

Yeah, exactly. The embedded spec is 100% guaranteed to be in-sync with your code and middleware should use it. It never even occurred to me that people wouldn't set this option :)

mromaszewicz avatar Jun 06 '24 14:06 mromaszewicz