diff --git a/docs/source/api.rst b/docs/source/api.rst index e36a66190..ae7d707f8 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -92,6 +92,32 @@ Chalice you would like more control over how CORS is configured, you can provide an instance of :class:`CORSConfig`. + .. method:: authorizer(name, \*\*options) + + Register a built-in authorizer. + + .. code-block:: python + + from chalice import Chalice, AuthResponse + + app = Chalice(app_name="appname") + + @app.authorizer(ttl_seconds=30) + def my_auth(auth_request): + # Validate auth_request.token, and then: + return AuthResponse(routes=['/'], principal_id='username') + + @app.route('/', authorizer=my_auth) + def viewfunction(value): + pass + + :param ttl_seconds: The number of seconds to cache this response. + Subsequent requests that require this authorizer will use a + cached response if available. The default is 300 seconds. + + :param execution_role: An optional IAM role to specify when invoking + the Lambda function associated with the built-in authorizer. + Request ======= @@ -257,6 +283,66 @@ for an ``@app.route(authorizer=...)`` call: The header where the auth token will be specified. +Built-in Authorizers +-------------------- + +These classes are used when defining built-in authoriers in Chalice. + +.. class:: AuthRequest(auth_type, token, method_arn) + + An instance of this class is passed as the first argument + to an authorizer defined via ``@app.authorizer()``. You + generally do not instantiate this class directly. + + .. attribute:: auth_type + + The type of authentication + + .. attribute:: token + + The authorization token. This is usually the value of the + ``Authorization`` header. + + .. attribute:: method_arn + + The ARN of the API gateway being authorized. + +.. class:: AuthResponse(routes, principal_id, context=None) + + .. attribute:: routes + + A list of authorized routes. Each element in the list + can either by a string route such as `"/foo/bar"` or + an instance of ``AuthRoute``. If you specify the URL as + a string, then all supported HTTP methods will be authorized. + If you want to specify which HTTP methods are allowed, you + can use ``AuthRoute``. + + .. attribute:: principal_id + + The principal id of the user. + + .. attribute:: context + + An optional dictionary of key value pairs. This dictionary + will be accessible in the ``app.current_request.context`` + in all subsequent authorized requests for this user. + +.. class:: AuthRoute(path, methods) + + This class be used in the ``routes`` attribute of a + :class:`AuthResponse` instance to get fine grained control + over which HTTP methods are allowed for a given route. + + .. attribute:: path + + The allowed route specified as a string + + .. attribute:: methods + + A list of allowed HTTP methods. + + APIGateway ========== diff --git a/docs/source/index.rst b/docs/source/index.rst index 2e466f2e1..efee2d207 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -64,6 +64,7 @@ Topics topics/packaging topics/pyversion topics/cfn + topics/authorizers API Reference diff --git a/docs/source/topics/authorizers.rst b/docs/source/topics/authorizers.rst new file mode 100644 index 000000000..b5c0afc06 --- /dev/null +++ b/docs/source/topics/authorizers.rst @@ -0,0 +1,219 @@ +Authorization +============= + +Chalice supports multiple mechanisms for authorization. This topic +covers how you can integrate authorization into your Chalice applications. + +In Chalice, all the authorizers are configured per-route and specified +using the ``authorizer`` kwarg to an ``@app.route()`` call. You +control which type of authorizer to use based on what's passed as the +``authorizer`` kwarg. You can use the same authorizer instance for +multiple routes. + +The first set of authorizers chalice supports cover the scenario where +you have some existing authorization mechanism that you just want your +Chalice app to use. + +Chalice also supports built-in authorizers, which allows Chalice to +manage your custom authorizers as part of ``chalice deploy``. This is +covered in the Built-in Authorizers section. + + +AWS IAM Authorizer +------------------ + +The IAM Authorizer allows you to control access to API Gateway with +`IAM permissions`_ + +To associate an IAM authorizer with a route in chalice, you use the +:class:`IAMAUthorizer` class: + +.. code-block:: python + + authorizer = IAMAuthorizer() + + @app.route('/iam-auth', methods=['GET'], authorizer=authorizer) + def authenticated(): + return {"success": True} + + +See the `API Gateway documentation +`__ +for more information on controlling access to API Gateway with IAM permissions. + +Amazon Cognito User Pools +------------------------- + +In addition to using IAM roles and policies with the :class:`IAMAuthorizer` you +can also use a `Cognito user pools`_ to control who can access your Chalice +app. A cognito user pool serves as your own identity provider to maintain a +user directory. + +To integrate Cognito user pools with Chalice, you'll need to have an existing +cognito user pool configured. + + +.. code-block:: python + + authorizer = CognitoUserPoolAuthorizer( + 'MyPool', provider_arns=['arn:aws:cognito:...:userpool/name']) + + @app.route('/user-pools', methods=['GET'], authorizer=authorizer) + def authenticated(): + return {"sucecss": True} + + +Within a request, you can access the ``app.current_request.context`` object +for metadata about the authenticated request. + +For more information about using Cognito user pools with API Gateway, +see the `Use Amazon Cognito User Pools documentation +`__. + + +Custom Authorizers +------------------ + +API Gateway also lets you write custom authorizers using a Lambda function. +You can configure a Chalice route to use a pre-existing Lambda function as +a custom authorizer. If you also want to write and manage your Lambda +authorizer using Chalice, see the next section, Built-in Authorizers. + +To connect an existing Lambda function as a custom authorizer in chalice, +you use the ``CustomAuthorizer`` class: + +.. code-block:: python + + authorizer = CustomAuthorizer( + 'MyCustomAuth', header='Authorization', + authorizer_uri=('arn:aws:apigateway:region:lambda:path/2015-03-01' + '/functions/arn:aws:lambda:region:account-id:' + 'function:FunctionName/invocations')) + + @app.route('/custom-auth', methods=['GET'], authorizer=authorizer) + def authenticated(): + return {"success": True} + + +Built-in Authorizers +-------------------- + +The ``IAMAuthorizer``, ``CognitoUserPoolAuthorizer``, and the +``CustomAuthorizer`` classes are all for cases where you have existing +resources for managing authorization and you want to wire them together with +your Chalice app. A Built-in authorizer is used when you'd like to write your +custom authorizer in Chalice, and have the additional Lambda functions managed +when you run ``chalice deploy/delete``. This section will cover how to use the +built-in authorizers in chalice. + +Creating an authorizer in chalice requires you use the ``@app.authorizer`` +decorator to a function. The function must accept a single arg, which will be +an instance of :class:`AuthRequest`. The function must return a +:class:`AuthResponse`. As an example, we'll port the example from the `API +Gateway documentation`_. First, we'll show the code and then walk through it: + +.. code-block:: python + + from chalice import Chalice, AuthResponse + + app = Chalice(app_name='demoauth1') + + + @app.authorizer() + def demo_auth(auth_request): + token = auth_request.token + # This is just for demo purposes as shown in the API Gateway docs. + # Normally you'd call an oauth provider, validae the + # jwt token, etc. + # In this exampe, the token is treated as the status for demo + # purposes. + if token == 'allow': + return AuthResponse(routes=['/'], principal_id='user') + else: + return AuthResponse(routes=[], principal_id='user') + + + @app.route('/', authorizer=demo_auth) + def index(): + return {'context': app.current_request.context} + + +In the example above we define a built-in authorizer by decorating +the ``demo_auth`` function with the ``@app.authorizer()`` decorator. +Note you must use ``@app.authorizer()`` and not ``@app.authorizer``. +A built-in authorizer function has this type signature:: + + def auth_handler(auth_request: AuthRequest) -> AuthResponse: ... + +Within the auth handler you must determine if the request is +authorized or not. The ``AuthResponse`` contains the allowed +URLs as well as the principal id of the user. You can optionally +return a dictionary of key value pairs (as the ``context`` kwarg). +This dictionary will be passed through on subsequent requests. +In our example above we're not using the context dictionary. + +Now let's deploy our app. As usual, we just need to run +``chalice deploy`` and chalice will automatically deploy all the +necessary Lambda functions for us. + +Now when we try to make a request, we'll get an Unauthorized error:: + + $ http https://api.us-west-2.amazonaws.com/dev/ + HTTP/1.1 401 Unauthorized + + { + "message": "Unauthorized" + } + +If we add the appropriate authorization header, we'll see the call succeed:: + + $ http https://api.us-west-2.amazonaws.com/dev/ 'Authorization: allow' + HTTP/1.1 200 OK + + { + "context": { + "accountId": "12345", + "apiId": "api", + "authorizer": { + "principalId": "user" + }, + "httpMethod": "GET", + "identity": { + "accessKey": null, + "accountId": null, + "apiKey": "", + "caller": null, + "cognitoAuthenticationProvider": null, + "cognitoAuthenticationType": null, + "cognitoIdentityId": null, + "cognitoIdentityPoolId": null, + "sourceIp": "1.1.1.1", + "user": null, + "userAgent": "HTTPie/0.9.9", + "userArn": null + }, + "path": "/dev/", + "requestId": "d35d2063-56be-11e7-9ce1-dd61c24a3668", + "resourceId": "id", + "resourcePath": "/", + "stage": "dev" + } + } + +The low level API for API Gateway's custom authorizer feature requires +that an IAM policy must be returned. The :class:`AuthResponse` class we're +using is a wrapper over building the IAM policy ourself. If you want +low level control and would prefer to contruct the IAM policy yourself +you can return a dictionary of the IAM policy instead of an instance of +:class:`AuthResponse`. If you do that, the dictionary is returned +without modification back to API Gateway. + +For more information on custom authorizers, see the +`Use API Gateway Custom Authorizers +`__ +page in the API Gateway user guide. + + +.. _IAM permissions: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_permissions.html +.. _Cognito User Pools: http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html +.. _API Gateway documentation: http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html#api-gateway-custom-authorizer-lambda-function-create