Policies
A policy is a list of named checks run against a subject. The names are the documentation: reading a policy tells you what the request has to satisfy, without reading a method body.
class AuthorizationPolicy < Policy uses ClientPolicy checks :response_type_is_supported, :client_may_use_the_code_grant, :pkce_is_present_when_required, :challenge_method_is_supported, :scopes_are_permitted, :resources_are_absoluteendusesruns another policy first, in full.AuthorizationPolicycannot check scopes against a client it has not yet established exists, soClientPolicyruns ahead of it.checksnames methods on this policy. They run in declaration order.
Both inherit, so a subclass gets its parent’s checks and can add to them.
Denials carry two things
Section titled “Denials carry two things”def pkce_is_present_when_required if client.public? && code_challenge.blank? deny!("invalid_request", "a public client must send code_challenge", redirectable: true) endendAn OAuth error code, and whether the failure is safe to hand back to the client’s
redirect_uri.
That second flag is the load-bearing one. invalid_client and an unregistered redirect_uri are
not redirectable — if they were, an attacker could point redirect_uri anywhere and use the error
response itself as an open redirector. Everything after the client and its redirect URI have been
established is redirectable, because by then the destination is one the client registered.
rescue Policy::Denied => denial if denial.redirectable && authorization.client&.redirect_uri?(authorization.redirect_uri) redirect_to authorization.redirect_with(issuer:, error: denial.error, …) else render :error, status: denial.status endThe non-redirectable branch renders a page rather than a JSON body, because a refusal with nowhere safe to send it is one a person is looking at. A client that asks for JSON still gets it.
Writing one
Section titled “Writing one”Subclass Policy, delegate what you need from the subject, and keep each check to one assertion.
class ClientPolicy < Policy checks :client_is_known, :redirect_uri_is_registered
delegate :client, :redirect_uri, to: :subject
private
def client_is_known deny!("invalid_client", "no client is registered with that client_id") if client.nil? end
def redirect_uri_is_registered if redirect_uri.blank? deny!("invalid_request", "redirect_uri is required") elsif !client.redirect_uri?(redirect_uri) deny!("invalid_request", "redirect_uri is not registered for this client") end endendThen run it: ClientPolicy.new(authorization).call.
