logo

Lack of data validation - Token - Ruby


Need

Implementation of proper data validation for JWT access tokens


Context

  1. Usage of Ruby for building web applications and scripting
  2. Usage of JWT for secure authentication and authorization

Description

Insecure Code Example

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

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

Secure Code Example

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

  • 353 - Lack of data validation - Token

  • Last updated

    2023/09/18