Skip to main content

Lack of data validation - Token

Need

Implementation of proper data validation for JWT access tokens

Context

  • Usage of Ruby for building web applications and scripting
  • Usage of JWT for secure authentication and authorization

Description

Non compliant code

require 'jwt'

class ApplicationController < ActionController::API
before_action :authenticate_request

attr_reader :current_user

private

def authenticate_request
@current_user = decode_authentication_command.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless @current_user
end

def decode_authentication_command
@decode_authentication_command ||= DecodeAuthenticationCommand.new
end
end

class DecodeAuthenticationCommand < Command
def initialize(headers = {})
@headers = headers
@errors = {}
end

def call
user_id = decoded_auth_token['user_id'] if decoded_auth_token
User.find(user_id) if user_id
end

private

def decoded_auth_token
@decoded_auth_token ||= JWT.decode(http_auth_header, nil, false)[0]
end

def http_auth_header
if @headers['Authorization'].present?
return @headers['Authorization'].split(' ').last
end
@errors[:token] = 'Missing token'
end
end

In the above code, the Ruby on Rails application is using JWT (JSON Web Tokens) for authentication. The token is being sent in the 'Authorization' header of the HTTP request.

The DecodeAuthenticationCommand class is responsible for decoding the JWT. It does this in the decoded_auth_token method, where it uses the JWT.decode method.

The issue lies in the use of JWT.decode(http_auth_header, nil, false)[0]. The third parameter to JWT.decode is a boolean that specifies whether to validate the signature of the token. By setting this to false, the application is not validating the signature of the JWT.

This means that an attacker could modify the payload of the JWT, or even remove the signature entirely, and the application would still accept it as valid. This could potentially allow an attacker to impersonate any user, or to elevate their privileges.

Steps

  • Implement signature validation for the JWT access token.
  • Use a secure algorithm for signing the JWT access token, such as HMAC-SHA256 or RSA-SHA256.
  • Verify the signature of the JWT access token before accepting it.
  • If the signature is invalid or missing, reject the request and return an appropriate error message.
  • Ensure that the JWT access token cannot be modified by implementing measures such as encryption or signing the token payload.
  • Consider using a library or framework that provides built-in JWT validation and verification functionality.
  • Regularly update the JWT library or framework to ensure that any security vulnerabilities are patched.

Compliant code

require 'jwt'

class ApplicationController < ActionController::API
before_action :authenticate_request

attr_reader :current_user

private

def authenticate_request
@current_user = decode_authentication_command.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless @current_user
end

def decode_authentication_command
@decode_authentication_command ||= DecodeAuthenticationCommand.new
end
end

class DecodeAuthenticationCommand < Command
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s

def initialize(headers = {})
@headers = headers
@errors = {}
end

def call
user_id = decoded_auth_token['user_id'] if decoded_auth_token
User.find(user_id) if user_id
end

private

def decoded_auth_token
@decoded_auth_token ||= JWT.decode(http_auth_header, SECRET_KEY, true, { algorithm: 'HS256' })[0]
rescue JWT::DecodeError => e
@errors[:token] = e.message
end

def http_auth_header
if @headers['Authorization'].present?
return @headers['Authorization'].split(' ').last
end
@errors[:token] = 'Missing token'
end
end

The updated code now includes signature validation for the JWT access token. The JWT.decode method now includes the SECRET_KEY and true as arguments, which means it will verify the signature of the JWT token using the HS256 algorithm. If the signature is invalid or missing, a JWT::DecodeError will be raised and caught, and the error message will be stored in @errors[:token].

The SECRET_KEY is retrieved from the Rails application secrets, which should be stored securely and not be accessible in the code or version control system. This key is used to sign the JWT token and to verify the signature of incoming JWT tokens.

The http_auth_header method remains the same, it retrieves the token from the 'Authorization' header of the incoming request. If the header is missing, an error message is stored in @errors[:token].

The call method retrieves the user_id from the decoded JWT token (if it exists) and finds the corresponding User. If the user_id is not present or if the User does not exist, the method will return nil.

The authenticate_request method in the ApplicationController calls the decode_authentication_command before every action. If the @current_user is not set (which means the JWT token was invalid or the User does not exist), it will render a 'Not Authorized' error message with a status of 401.

References