I am working on configuring security for my rails application. At this point the user can authenticate and get access tokens to submit to the API, and the API can validate those tokens before an endpoint is invoked. Now I would like to validate the scope of the token, but what scopes are required are really an endpoint specific matter.
The simplest way would be to create a validate_scope!
method and just call that at the top of each request; but I'd really prefer to keep it out of the endpoint request handler and instead find a way to add this to, say, the request definition. Maybe something like this:
get 'endpoint', required_scopes: ['custom.scope'] do
...
end
Or perhaps something similar to the params
block placed just before the endpoint.
params do
..
end
claims do
requires scope type: [String] do
requires 'custom.scope'
end
end
get 'endpoint' do
..
end
Are there any existing solutions for this, or perhaps some documentation that can point me in the right direction for writing my own such validation?
I also want to integrate with AWS Verified Permissions later, for which I'll want to setup a similar validation call for particular endpoints.
After digging into the doc + github code a bit, I was able to come up with a solution. First, you can add metadata to an endpoint trivially in one of two ways. The first way is to specify an options object that gets merged into the route's options object - at the top-level. This can be done like so:
get 'my-endpoint', { 'custom': 'option' } do
...
end
Using the active route
object within a before { ... }
block, you can grab those options like so:
before {
custom = route.options.custom
...
}
Alternatively, and perhaps preferably so as to avoid any potential collisions, you can use route_setting
to add to the settings object (which seems to be empty be default).
route_setting :custom, "value"
get 'my-endpoint' do
...
end
You could then access the route settings like so:
before {
custom = route.settings[:custom]
...
}
With either approach (but I'll favor the later), you can now add various details for your authentication and authorization flows. For example, we can add a :public?
setting used to skip authentication + authorization, and a :scopes
parameter that we can use to further validate the token.
route_setting :public?, true
get 'my-public-route' do
...
end
route_setting :scopes, ['custom-scope'].to_set
get 'my-authenticated-route' do
...
end
Then use a common before {...}
block prior to mounting any api modules or endpoints and evaluate the active route
object like so:
before {
next if route.settings[:public?]
authenticate!
if route.settings.key?(:scopes)
error!(403, 'Forbidden') unless token_scopes().superset?(route.settings[:scopes])
end
# additional authorization
}