class JWT::Rack::Auth
Authentication middleware
Constants
- BEARER_TOKEN_REGEX
The last segment gets dropped for 'none' algorithm since there is no signature so both of these patterns are valid. All character chunks are base64url format and periods.
Bearer abc123.abc123.abc123 Bearer abc123.abc123.
- DEFAULT_ALGORITHM
- ERRORS_TO_RESCUE
- InvalidAuthHeaderFormat
- JWT_DECODE_ERRORS
- MissingAuthHeader
- SUPPORTED_ALGORITHMS
Attributes
exclude[R]
options[R]
secret[R]
verify[R]
Public Class Methods
new(app, opts = {})
click to toggle source
Initialization should fail fast with an ArgumentError if any args are invalid.
# File lib/jwt/rack/auth.rb, line 64 def initialize(app, opts = {}) @app = app @secret = opts.fetch(:secret, nil) @verify = opts.fetch(:verify, true) @options = opts.fetch(:options, {}) @exclude = opts.fetch(:exclude, []) @on_error = opts.fetch(:on_error, method(:default_on_error)) @secret = @secret.strip if @secret.is_a?(String) @options[:algorithm] = DEFAULT_ALGORITHM if @options[:algorithm].nil? check_secret_type! check_secret! check_secret_and_verify_for_none_alg! check_verify_type! check_options_type! check_valid_algorithm! check_exclude_type! check_on_error_callable! end
Public Instance Methods
call(env)
click to toggle source
# File lib/jwt/rack/auth.rb, line 86 def call(env) if path_matches_excluded_path?(env) @app.call(env) else verify_token(env) end end
Private Instance Methods
check_exclude_type!()
click to toggle source
# File lib/jwt/rack/auth.rb, line 152 def check_exclude_type! raise ArgumentError, 'exclude argument must be an Array' unless @exclude.is_a?(Array) @exclude.each do |x| raise ArgumentError, 'each exclude Array element must be a String' unless x.is_a?(String) raise ArgumentError, 'each exclude Array element must not be empty' if x.empty? unless x.start_with?('/') raise ArgumentError, 'each exclude Array element must start with a /' end end end
check_on_error_callable!()
click to toggle source
# File lib/jwt/rack/auth.rb, line 166 def check_on_error_callable! unless @on_error.respond_to?(:call) raise ArgumentError, 'on_error argument must respond to call' end end
check_options_type!()
click to toggle source
# File lib/jwt/rack/auth.rb, line 140 def check_options_type! raise ArgumentError, 'options argument must be a Hash' unless options.is_a?(Hash) end
check_secret!()
click to toggle source
# File lib/jwt/rack/auth.rb, line 118 def check_secret! if @secret.nil? || (@secret.is_a?(String) && @secret.empty?) unless @options[:algorithm] == 'none' raise ArgumentError, 'secret argument can only be nil/empty for the "none" algorithm' end end end
check_secret_and_verify_for_none_alg!()
click to toggle source
# File lib/jwt/rack/auth.rb, line 126 def check_secret_and_verify_for_none_alg! if @options && @options[:algorithm] && @options[:algorithm] == 'none' unless @secret.nil? && @verify.is_a?(FalseClass) raise ArgumentError, 'when "none" the secret must be "nil" and verify "false"' end end end
check_secret_type!()
click to toggle source
# File lib/jwt/rack/auth.rb, line 112 def check_secret_type! unless Token.secret_of_valid_type?(@secret) raise ArgumentError, 'secret argument must be a valid type' end end
check_valid_algorithm!()
click to toggle source
# File lib/jwt/rack/auth.rb, line 144 def check_valid_algorithm! unless @options && @options[:algorithm] && SUPPORTED_ALGORITHMS.include?(@options[:algorithm]) raise ArgumentError, 'algorithm argument must be a supported type' end end
check_verify_type!()
click to toggle source
# File lib/jwt/rack/auth.rb, line 134 def check_verify_type! unless verify.is_a?(TrueClass) || verify.is_a?(FalseClass) raise ArgumentError, 'verify argument must be true or false' end end
default_on_error(error)
click to toggle source
# File lib/jwt/rack/auth.rb, line 188 def default_on_error(error) error_message = { ::JWT::DecodeError => 'Invalid JWT token : Decode Error', ::JWT::VerificationError => 'Invalid JWT token : Signature Verification Error', ::JWT::ExpiredSignature => 'Invalid JWT token : Expired Signature (exp)', ::JWT::IncorrectAlgorithm => 'Invalid JWT token : Incorrect Key Algorithm', ::JWT::ImmatureSignature => 'Invalid JWT token : Immature Signature (nbf)', ::JWT::InvalidIssuerError => 'Invalid JWT token : Invalid Issuer (iss)', ::JWT::InvalidIatError => 'Invalid JWT token : Invalid Issued At (iat)', ::JWT::InvalidAudError => 'Invalid JWT token : Invalid Audience (aud)', ::JWT::InvalidSubError => 'Invalid JWT token : Invalid Subject (sub)', ::JWT::InvalidJtiError => 'Invalid JWT token : Invalid JWT ID (jti)', ::JWT::InvalidPayload => 'Invalid JWT token : Invalid Payload', MissingAuthHeader => 'Missing Authorization header', InvalidAuthHeaderFormat => 'Invalid Authorization header format' } message = error_message.fetch(error.class) body = { error: message }.to_json headers = { 'Content-Type' => 'application/json', 'Content-Length' => body.bytesize.to_s } [401, headers, [body]] end
invalid_auth_header?(env)
click to toggle source
# File lib/jwt/rack/auth.rb, line 180 def invalid_auth_header?(env) !valid_auth_header?(env) end
missing_auth_header?(env)
click to toggle source
# File lib/jwt/rack/auth.rb, line 184 def missing_auth_header?(env) env['HTTP_AUTHORIZATION'].nil? || env['HTTP_AUTHORIZATION'].strip.empty? end
path_matches_excluded_path?(env)
click to toggle source
# File lib/jwt/rack/auth.rb, line 172 def path_matches_excluded_path?(env) @exclude.any? { |ex| env['PATH_INFO'].start_with?(ex) } end
valid_auth_header?(env)
click to toggle source
# File lib/jwt/rack/auth.rb, line 176 def valid_auth_header?(env) env['HTTP_AUTHORIZATION'] =~ BEARER_TOKEN_REGEX end
verify_token(env)
click to toggle source
# File lib/jwt/rack/auth.rb, line 96 def verify_token(env) raise MissingAuthHeader if missing_auth_header?(env) raise InvalidAuthHeaderFormat if invalid_auth_header?(env) # extract the token from the Authorization: Bearer header # with a regex capture group. token = BEARER_TOKEN_REGEX.match(env['HTTP_AUTHORIZATION'])[1] decoded_token = Token.decode(token, @secret, @verify, @options) env['jwt.payload'] = decoded_token.first env['jwt.header'] = decoded_token.last @app.call(env) rescue *ERRORS_TO_RESCUE => e @on_error.call(e) end