apiflask / apiflask

A lightweight Python web API framework.

Home Page:https://apiflask.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Specifying a security scheme always includes a default

chouinar opened this issue · comments

When overriding the app.security_schemes value as defined on https://apiflask.com/authentication/ with a custom key, the ApiKeyAuth value is always present. ApiKeyAuth ends up a duplicate of the one I added.

On the docs endpoint, the available authorizations ends up including the unspecified ApiKeyAuth as well as my new key:
Screen Shot 2023-03-01 at 2 43 27 PM

In the openapi.json file generated, the security schema looks like:

"securitySchemes":{"ApiKeyAuth":{"in":"header","name":"X-Example-Key","type":"apiKey"},"api_token":{"in":"header","name":"X-Example-Key","type":"apiKey"}}}

A minimal working app that hits this issue:

import typing as t
from flask import current_app
from apiflask import APIFlask, HTTPTokenAuth, Schema, abort
from apiflask.fields import String
from jose import jwt

app = APIFlask(__name__)

auth = HTTPTokenAuth(header="X-Example-Key")

app.security_schemes = {
    "api_token": {"type": "apiKey", "name": "X-Example-Key", "in": "header"},
}

app.config['SECRET_KEY'] = 'secret-key'

class User:
    def __init__(self, id: int, secret: str):
        self.id = id
        self.secret = secret

    def get_token(self):
        header = {'alg': 'HS256'}
        payload = {
            'id': self.id
        }
        return jwt.encode(
            header, payload, current_app.config['SECRET_KEY']
        ).decode()


users = [
    User(1, 'lorem'),
    User(2, 'ipsum'),
    User(3, 'test'),
]


def get_user_by_id(id: int) -> t.Union[User, None]:
    return tuple(filter(lambda u: u.id == id, users))[0]


@auth.verify_token
def verify_token(token: str) -> t.Union[User, None]:
    try:
        data = jwt.decode(
            token.encode('ascii'),
            current_app.config['SECRET_KEY'],
        )
        id = data['id']
        user = get_user_by_id[id]
    except Exception:
        return None
    return user


class Token(Schema):
    token = String()


@app.post('/token/<int:id>')
@app.output(Token)
def get_token(id: int):
    if get_user_by_id(id) is None:
        abort(404)
    return {
        'token': f'Bearer {get_user_by_id(id).get_token()}'
    }


@app.get('/name/<int:id>')
@app.auth_required(auth)
def get_secret():
    return auth.current_user.secret

Desired behavior: Specifying a security scheme should only include defined keys. Or there should be some way to exclude ApiKeyAuth. I did find a way to make the UI not display it by adding "ApiKeyAuth": {} to the definition, but it still appears as an empty object in the openapi json.

Some additional context - building an API with multiple HTTPAuthToken objects defined with different header values. We want to keep the naming specific and not have a generically named ApiKeyAuth value.

Environment:

  • Python version: 3.11.0
  • Flask version: 2.2.3
  • APIFlask version: 1.2.3

Hi. The app.security_schemes is used to add custom security schemes when using third-party auth libraries (maybe it should be named with app.additional_security_schemes :P). When using the HTTPBasicAuth or HTTPTokenAuth, the corresponding security scheme will always be generated automatically.

For your use case, I think we could add a parameter to the HTTPTokenAuth class. Something like this:

auth = HTTPTokenAuth(header="X-Example-Key", security_scheme_name="api_token")
auth2 = HTTPTokenAuth(header="X-Example-Key", security_scheme_name="api_token2")

What do you think? Will this meet your needs?