Skip to content

Instantly share code, notes, and snippets.

@eduardinni
Last active March 9, 2024 08:54
Show Gist options
  • Save eduardinni/77937cfb4148a8144d80c7863ee1ccd6 to your computer and use it in GitHub Desktop.
Save eduardinni/77937cfb4148a8144d80c7863ee1ccd6 to your computer and use it in GitHub Desktop.
Ruby on Rails JWT

Ruby on Rails API authorization via JWT

ApplicationController that defines methods to authorize requests based on JWT, also defines Error handling

class ApplicationController < ActionController::API
  before_action :authenticate
  before_action :set_current_user_refs
  before_action :authorize

  class NotAuthorized < StandardError
  end
  rescue_from NotAuthorized, with: :not_authorized

  class InsufficientParams < StandardError
  end
  rescue_from InsufficientParams, with: :insufficient_params
  
  class InternalServerError < StandardError
  end
  rescue_from InternalServerError, with: :internal_server_error

  def ping
    render json: { res: "pong" }
  end

  def not_found
    render json: { error: "not found" }, status: 404
  end

  def authenticate
    render json: { error: "unauthorized" }, status: 401 unless valid_id_token?
  end

  def valid_id_token?
    return false unless auth_header_present?

    verify_id_token(get_id_token)
  end

  private
    def auth_header_present?
      !!request.env.fetch("HTTP_AUTHORIZATION", "").scan(/Bearer/).flatten.first
    end

    def get_id_token
      request.env["HTTP_AUTHORIZATION"].scan(/Bearer (.*)$/).flatten.last
    end

    def verify_id_token(id_token)
      begin
        cognito_jwks = JSON.parse(File.read(Rails.root.join('config/jwks.json')))
        # cognito_jwks = {"keys"=>[{"alg"=>"RS256", "e"=>"AQAB", "kid"=>"6YPwROJiWaClmWi7Q4okTGIgJS78p35ls7Y3eNMq1gg=", "kty"=>"RSA", "n"=>"tOLjgUMsT29AavKD0LoKtRIxvtIH9_KhrgNUh5NLYGqRxBPyCsLio7gqiCo52WZ1rbnGfDkcbjEr6UfVkAJ0Ym1XaSyt-t0DGHjO_MTn44HE6uD95690I8xP02r2IQrgbUl9aUGjg1KCRf6vcPr-0DtxUN3pKQcw55ODG3O56imCQvQfw9Q66l8nGKnd3cqjwsSeG2BXRMQOEHPDjVdocTyK37-xwhiJNWtnauPuUa2_XVLKia9PRqjRkdysc68Oh_N7HD-JkfyflIqnNkRsWQYYOEs3BRGis_ib-nr75uKHrCQR9hG6CmvlG6V07u0NGtkCjyaCuzCJo-KYim2tOQ", "use"=>"sig"}, {"alg"=>"RS256", "e"=>"AQAB", "kid"=>"o6GR+dnhKzcl2mreFN9OE2+sMxRitKnKCe0pGIOTFeI=", "kty"=>"RSA", "n"=>"zKvF0uq5xj4VpXoKy76jxsf_up2xHbyaniH-EvjFSuPP6mxpH3GiOFnKftOxxdp6MiEgMysYY5K5nVBq0uczUqp_OCVOCgXId2_CT1iop8Y2trpzAL8BG8MC7TpfbqajgPpOlUOrW2m8jCo3eYNDp1sOiI1JDDNDFYb03UXlKMJ6ZzABPPbB57lXBOHxqqDhfhSq6aebRXDHHfrt6hi4SeCK78UNq7rgpIoWIlfNPuKVq4aYOShs5hgRuwTrcZ32MkNfnJzNWmzm7_GfWEPoUFXBOCItZwX7HENUybD8QkdjpbDieyecyUp5EXgtM5jDoCMp_Y5no93cEhKYKxKlbw", "use"=>"sig"}]} 

        id_token_header = id_token.split('.')[0]
        decoded_id_token_header = JSON.parse(Base64.decode64(id_token_header))

        kid = decoded_id_token_header['kid']
        # Find its corresponding jwk by kid
        jwk = cognito_jwks["keys"].detect { |jwk| jwk["kid"] == kid }
        jwk = JSON::JWK.new(jwk)

        valid_jwt = JSON::JWT.decode(id_token, jwk)
        groups = valid_jwt['cognito:groups']

        if groups.include?('admin') or groups.include?('superadmin')
          @current_user = {
            id:    valid_jwt[:sub],
            iat:   valid_jwt[:iat],
            exp:   valid_jwt[:exp],
            group: valid_jwt['cognito:groups'][0]
            # group: 'admin'
          }
          return true
        else
          @current_user = nil
          return false
        end
      rescue JSON::JWS::VerificationFailed
        @current_user = nil
        return false
      rescue JSON::JWT::InvalidFormat
        @current_user = nil
        return false
      end

      false
    end

    def set_current_user_refs
      if @current_user[:group] == 'superadmin'
        @current_user[:organization_id] = UsersOrganization.where(id: @current_user[:id]).pluck(:organization_id).first
        raise NotAuthorized unless @current_user[:organization_id].present?
        @current_user[:organization_unit_ids] = OrganizationUnit.where(organization_id: @current_user[:organization_id]).pluck(:id)
      elsif @current_user[:group] == 'admin'
        @current_user[:organization_unit_ids] = UsersOrganizationUnit.where(id: @current_user[:id]).pluck(:organization_unit_id)
        raise NotAuthorized unless @current_user[:organization_unit_ids].count > 0
        @current_user[:organization_id] = OrganizationUnit.where(id: @current_user[:organization_unit_ids][0]).pluck(:organization_id).first
      end
    end

    def authorize
      if params[:organization_id].present?
        raise NotAuthorized unless @current_user[:organization_id] == params[:organization_id]
      end

      if params[:organization_unit_id].present?
        raise NotAuthorized unless @current_user[:organization_unit_ids].include?(params[:organization_unit_id])
      end
    end

    def not_authorized(e)
      render json: { error: true, errorMsg: 'not authorized' }, status: :unauthorized
    end

    def insufficient_params(e)
      render json: { error: true, errorMsg: 'insufficient params' }, status: :bad_request
    end

    def not_found(e)
      render json: { error: true, errorMsg: 'not found' }, status: :not_found
    end

    def internal_server_error(e)
      render json: { error: 'internal server error' }, status: :internal_server_error
    end

end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment