Authorizers
Important: These docs are for the outdated Jets 5 versions and below. For the latest Jets docs: docs.rubyonjets.com
Note: You may also want to consider more Jets-based auth solutions instead of APIGW Authorizers.
Jets supports writing Lambda Authorizers. You define Lambda Authorizers in the app/authorizers
folder. Here’s an example:
app/authorizers/main_authorizer.rb:
class MainAuthorizer < ApplicationAuthorizer
authorizer(
name: "MyAuthorizer",
identity_source: "Auth", # maps to method.request.header.Auth
type: :request, # valid values: token, cognito_user_pools, request. Jets upcases internally.
)
def protect
puts "event #{JSON.dump(event)}" # Example event payload https://bit.ly/3Nbr2Dp
# Must conform to Amazon API Gateway Lambda Authorizer Output structure
# https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html
resource = event[:methodArn] # IE: arn:aws:execute-api:us-west-2:112233445566:ymy8tbxw7b/*/GET/my/path"
{
principalId: "current_user_id", # replace with the current user id
policyDocument: {
Version: "2012-10-17",
Statement: [
{
Action: "execute-api:Invoke",
Effect: "Allow",
Resource: resource
}
]
}
}
end
end
The authorizer
keyword builds an ApiGateway Authorizer. The authorizer options map to CloudFormation ApiGateway::Authorizer properties. You can use any of the properties supported by CloudFormation. The authorizer is associated with the Lambda function directly below it.
The Lambda function must return a response in the Amazon API Gateway Lambda Authorizer Output structure. The example above builds the raw structure, you may also be interested in the build_policy helper.
ApplicationAuthorizer
If your project does not yet have the ApplicationAuthorizer
yet, you can simply add it.
app/authorizers/application_authorizer.rb:
class ApplicationAuthorizer < Jets::Authorizer::Base
end
Connecting to Routes
To connect the Authorizer to an ApiGateway Method use the authorizer
property on a route. Example:
config/routes.rb:
Jets.application.routes.draw do
# main#protect => MainAuthorizer#protect in app/authorizers/main_authorizer.rb
get "hello", to: "posts#index", authorizer: "main#protect"
# ...
end
When the authorizer
property is used, the authorization_type
is inferred from the authorizer property and automatically set for you. The authorization_type
can be overridden by setting it explicitly. Refer to the Authorization Types docs for more info. For CUSTOM
AND COGNITO_USER_POOLS
authorization types, it is recommended to let Jets handle it. For the AWS_IAM
type you will need to handle it appropriately.
Authorizer in Controllers
You can set the authorizer in the controller instead of the routes.rb
file. Example:
class PostsController < ApplicationController
authorizer "main#protect" # protects all actions in the controller
end
You can also use only
and except
options to select which actions to protect.
class PostsController < ApplicationController
authorizer "main#protect", only: %w[index]
end
An except
example:
class PostsController < ApplicationController
authorizer "main#protect", except: %w[index]
end
Setting the authorizer in the controller is just syntactical sugar. Ultimately, the authorizer is still set at the API Gateway Method Resource.
Test Authorizer
To test the Authorizer, send a request with the Auth
header and one without it. Here’s an example:
$ URL=https://wmtqdx6byd.execute-api.us-west-2.amazonaws.com/dev/posts # use your own URL
$ curl -H "Auth: test" $URL -vso /dev/null 2>&1 | grep '< HTTP'
< HTTP/2 200
$ curl $URL -vso /dev/null 2>&1 | grep '< HTTP'
< HTTP/2 401
$
Authorizer Defaults
The authorizer
method has some conventional defaults. The following:
authorizer(
name: "MyAuthorizer",
)
Is the same as:
authorizer(
name: "MyAuthorizer",
identity_source: "Auth",
type: :request,
)
The only required option for the authorizer
method is name
. Also, the default identity_source
can be configured with config.api.authorizers.default_token_source
.
Jets.application.configure do
config.api.authorizers.default_token_source = "Auth" # method.request.header.Auth
end
Identity Source Convention
You may have noticed identity_source
understands a shorthand value: identity_source: "Auth"
. Jets expands it out to method.request.header.Auth
. For example:
authorizer(
name: "MyAuthorizer",
identity_source: "Auth",
)
is the same as:
authorizer(
name: "MyAuthorizer",
identity_source: "method.request.header.Auth",
)
If the identity_source
value contains a .
then Jets leaves it as-is. If it does not, then it will conventionally adds method.request.header.
. This also applies to comma-separated identity_source
values. Jets expands each item without a .
.
Authorizer Name Workaround
The ApiGateway::Authorizer CloudFormation docs state that the name is not required, but, in testing, have found it to be required. If you rename the lambda function or reassociate the authorizer with another Lambda function, the stack may roll back from a naming collision. To work around this, rename the authorizer for that deploy. Example:
class MainAuthorizer < ApplicationAuthorizer
authorizer(
name: "MyAuthorizer2", # <= Change this
identity_source: "Auth", # maps to method.request.header.Auth
type: :request, # valid values: token, cognito_user_pools, request. Jets upcases internally.
)
# ...
end
Before Filters
Note: You can also make use of Before Filters to build your own custom authorization system instead of using API Gateway Authorizers.