-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #725 from ShelterTechSF/validate-auth0-token
Validate auth0 token
- Loading branch information
Showing
4 changed files
with
91 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# frozen_string_literal: true | ||
|
||
# This concern is included in our ApplicationController. | ||
# It instantiates an Auth0Client, which calls the validate_token method | ||
# to validate Auth tokens included in requests from the front-end. | ||
module Secured | ||
extend ActiveSupport::Concern | ||
|
||
REQUIRES_AUTHENTICATION = { message: 'Requires authentication' }.freeze | ||
BAD_CREDENTIALS = { message: 'Bad credentials' }.freeze | ||
MALFORMED_AUTHORIZATION_HEADER = { | ||
error: 'invalid_request', | ||
error_description: 'Authorization header value must follow this format: Bearer access-token', | ||
message: 'Bad credentials' | ||
}.freeze | ||
|
||
# The authorize method can be run as a before_action within a controller to validate | ||
# a user's token prior to allowing access to an endpoint. | ||
def authorize | ||
token = token_from_request | ||
|
||
return if performed? | ||
|
||
validation_response = Auth0Client.validate_token(token) | ||
error = validation_response.error | ||
return unless error | ||
|
||
render json: { message: error.message }, status: error.status | ||
end | ||
|
||
private | ||
|
||
def token_from_request | ||
authorization_header_elements = request.headers['Authorization']&.split | ||
|
||
render json: REQUIRES_AUTHENTICATION, status: :unauthorized and return unless authorization_header_elements | ||
|
||
render json: MALFORMED_AUTHORIZATION_HEADER, status: :unauthorized and return unless authorization_header_elements.length == 2 | ||
|
||
scheme, token = authorization_header_elements | ||
|
||
render json: BAD_CREDENTIALS, status: :unauthorized and return unless scheme.downcase == 'bearer' | ||
|
||
token | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'jwt' | ||
require 'net/http' | ||
|
||
class Auth0Client | ||
Auth_Domain = URI::HTTPS.build(host: Rails.application.config.x.auth0.domain) | ||
Error = Struct.new(:message, :status) | ||
Response = Struct.new(:decoded_token, :error) | ||
|
||
def self.decode_token(token, jwks_hash) | ||
JWT.decode(token, nil, true, { | ||
algorithm: 'RS256', | ||
iss: URI.join(Auth_Domain, '/'), # Trailing slash is required to successfully validate token | ||
verify_iss: true, | ||
aud: Rails.application.config.x.auth0.audience, | ||
verify_aud: true, | ||
jwks: { keys: jwks_hash[:keys] } | ||
}) | ||
end | ||
|
||
def self.fetch_jwks | ||
jwks_uri = URI.join(Auth_Domain, '.well-known/jwks.json') | ||
Net::HTTP.get_response jwks_uri | ||
end | ||
|
||
def self.validate_token(token) | ||
jwks_response = fetch_jwks | ||
|
||
unless jwks_response.is_a? Net::HTTPSuccess | ||
error = Error.new(message: 'Unable to verify credentials', status: :internal_server_error) | ||
Response.new(nil, error) | ||
end | ||
|
||
jwks_hash = JSON.parse(jwks_response.body).deep_symbolize_keys | ||
decoded_token = decode_token(token, jwks_hash) | ||
Response.new(decoded_token, nil) | ||
rescue JWT::DecodeError | ||
Response.new(nil, Error.new('Bad credentials', :unauthorized)) | ||
end | ||
end |