noirbizarre / flask-restplus

Fully featured framework for fast, easy and documented API development with Flask
http://flask-restplus.readthedocs.org
Other
2.73k stars 506 forks source link

[proposal] better support for security requirements #26

Closed bigblind closed 9 years ago

bigblind commented 9 years ago

What I'm proposing, and willing to implement and contribute is a SecurityRequirement class, possibly with subclasses like OAuth2SecurityRequirement. defining an api with these requirements would look like this:

oauth2_req = OAuth2SecurityRequirement(name="needsuser", scopes=["scope1", "scope2"], flow="implicit", authorization_uri="http://example.com/authorize")
apikey_req = ApiKeyRequirement(name="apikey", param_name="key", in="query")

to require either one of these api requirements api-wide, you'd pass an array of instances to the API constructor.

```python
Api(title="my apy", version="v1", security_requirements=[
    apikey_req,
    oauth2_req("scope1")
])

Note that oauth2 requirement instances are callable, so you can pass in required scopes.

I'd be very much willing to implementthis and contribute the code back to this project if you're interested.

noirbizarre commented 9 years ago

It's interesting because it permit to use any framework.

But I'm wondering, apart from Oauthlib/Flask-Oauthlib, is there another alternative ?

Maybe we can be more opiniated and integrate Flask-oauthlib ?

bigblind commented 9 years ago

Hi @noirbizarre Sorry for my late reply.

I think it'd be good to allow people to document their API's security, without requiring a specific framework. However, having integration points where we can hook up other libraries would be cool. i'm using flask-oauthlib in my current project, and I don't think there's any other good one currently. points where we could integrate, are automatically getting the URLs for authorization and token endpoints, and automatically applying the require_oauth decorator when scopes are required.

satreix commented 9 years ago

I read the doc but I am wondering how security currently works in flask-restplus. I would like to add LDAP and token security and I do no get how to start doing so. Thx.

satreix commented 9 years ago

@noirbizarre I'm still trying to add security to my flask-restplus project and haven't yet found any decent exemple to do so. I would like to add a api-token or oauth2 authentication to my API. I've been fiddling with the 'security' api parameter but so far I came up with nothing.

Can someone add an exemple of sucha setup in the documentation ?

noirbizarre commented 9 years ago

Hi !

I documented a little bit more authorizations: http://flask-restplus.readthedocs.org/en/latest/documenting.html#documenting-autorizations

Everything is already supported into flask-restplus.

This is manual right now, but maybe in a futur it can handle OAuthlib to automatically extract OAuth information (and avoid duplicating declaration)

satreix commented 9 years ago

I added the following to my app:

authorizations = {
    'apikey': {
        'type': 'apiKey',
        'in': 'header',
        'name': 'X-API-KEY'
    }
}
api = Api(
    app,
    version='1.0',
    title='Assistant API',
    description='Access data from the comfort of a RESTfull API.',
    authorizations=authorizations,
    security='apikey'
)

The generated JSON seams to be correct:

{
    ...
    "security": [
        {
            "apikey": [ ]
        }
    ],
    "securityDefinitions": {
        "apikey": {
            "in": "header",
            "name": "X-API-KEY",
            "type": "apiKey"
        }
    },
    ...
}

But swagger-ui does not reflect any authentication, the API works without the token and the UI does not show any informations. Is it the normal behavior ?

noirbizarre commented 9 years ago

Security implementation is up to the developer right now.

You can right something like that:

def auth_required(func):
    func = api.doc(security='apikey')(func)
    def check_auth(*args, **kwargs):
        if 'X-API-KEY' not in request.headers:
            api.abort('401', 'API key required')
        key = request.headers['X-API-KEY']
        // Check key validity
        return func(*args, **kwargs)
    return check_auth

Then you only have to decorate the required method like this:

class MyResource(Resource):
    @auth_required
    def post(self):
         pass

They will be documented and checked.

The other alternative, if you're API always require a token, is to use the API decorators parameter so all you resource will be decorated:

api = Api(app, decorators=[auth_required])

On the Swagger UI side, you need to use this: https://github.com/swagger-api/swagger-ui#user-content-header-parameters or this: https://github.com/swagger-api/swagger-ui#user-content-custom-header-parameters---for-basic-auth-etc But you're right, maybe I should integrate this automatically, find a way to configure it.

merritts commented 8 years ago

+1 for this integration.

PeterParker commented 7 years ago

@noirbizarre -- Do you have any tips about best to interact with "the Swagger UI side" via the Flask RestPlus framework? It's not immediately obvious how one should "get at" swagger to enable something like https://github.com/swagger-api/swagger-ui#user-content-header-parameters.

jvleeuwen commented 7 years ago

@noirbizarre ive implemented youre suggestion and auth works nicely. On the other hand the summary and description does not load/work anymore for the methods. u have any suggestion how to solve it? if u need more information please let me know

livejake commented 6 years ago

@noirbizarre I'm trying to implement the following at namespace level with ns = Namespace('namespace name', description='my description',decorators=[auth_required]) My namespace doesn't seem to be picking up my decorator. Any ideas?

theholy7 commented 6 years ago

When using api.expect() payload validation seems to happen before the auth_required decorator is ran.

This leads to weird errors, where an unauthorized user can check which fields the payload can take by submitting empty payloads, because he gets the validation error instead of the auth error.

Has anyone faced this problem before?

PS: this happens when fields are required=True

honolulubill commented 6 years ago

@theholy7 Do you have the @expect before or after @auth_required when decorating your function?

theholy7 commented 6 years ago

I tried both ways and still got the error @honolulubill . But I created my own Resource and overwrote one of the functions:

class AuthorizedResource(Resource):
    """
    We created this AuthorizedResource from Resource
    because the user authorization was being made after payload
    validation. This way a non-auth'ed user was able to sniff the
    payload that was required by the endpoint.
    By wrapping dispatch_request with the requires_auth function,
    we check for user authentication first.
    """
    @requires_auth
    def pre_dispatch_request(self):
        """
        This method only exists so extended classes can do custom validations & checks that happen
        after authorization and before dispatch_request.
        """
        pass

    def dispatch_request(self, *args, **kwargs):
        self.pre_dispatch_request()
        validate_content_type()
        return super().dispatch_request(*args, **kwargs)