-
-
Save Krishna/0d86752c1d36d09c4698608441cb7f74 to your computer and use it in GitHub Desktop.
Token Authentication in Rails API Controller and Request Spec
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
# File: app/controllers/api/api_controller.rb | |
class Api::ApiController < ActionController::Base | |
# Consider subclassing ActionController::API instead of Base, see | |
# http://api.rubyonrails.org/classes/ActionController/API.html | |
protect_from_forgery with: :null_session | |
before_action :authenticate | |
def self.disable_turbolinks_cookies | |
skip_before_action :set_request_method_cookie | |
end | |
disable_turbolinks_cookies | |
private | |
def authenticate | |
# See http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html | |
authenticate_or_request_with_http_token do |token, _options| | |
# Perform token comparison, taking special care to | |
# avoid timing attacks and length leaks! | |
# See: https://thisdata.com/blog/timing-attacks-against-string-comparison/ | |
# Bad! Vulnerable to timing attacks: | |
# token == api_authentication_token # AVOID!! | |
# Not great, mitigate timing attacks but leaks length information: | |
# ActiveSupport::SecurityUtils.secure_compare(token, api_authentication_token) # AVOID!! | |
# OK, mitigate timing attacks and length leaks: | |
token_digest = ::Digest::SHA256.hexdigest(token) | |
api_auth_token_digest = ::Digest::SHA256.hexdigest(api_authentication_token) | |
ActiveSupport::SecurityUtils.secure_compare(token_digest, api_auth_token_digest) | |
# ActiveSupport::SecurityUtils.variable_size_secure_compare(token, api_authentication_token) # Alternative | |
end | |
end | |
def api_authentication_token | |
# Encryption option: https://github.com/rocketjob/symmetric-encryption | |
raise 'TODO: write your code here to lookup and decrypt an API token that is stored encrypted' | |
# Temporary option: | |
ENV.fetch('API_AUTHENTICATION_TOKEN') { raise 'Missing API token!' } | |
# If you are looking up a token per account or per user, do not use the submitted token | |
# as the lookup key as this is vulnerable to timing attacks. | |
# BAD: User.find_by_token(submitted_token).token # AVOID!!! | |
# Instead, use an alternative identifier that is not the token. E.g. one of | |
# email address, username, or some other value that is *not* the api secret token. | |
# OK: User.find_by_email(email).token | |
# Also OK: User.find_by_username(username).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
# File: spec/api/widgets_api_spec.rb OR spec/requests/widgets_api_spec.rb | |
require 'rails_helper' | |
describe 'Widgets API' do | |
describe 'POST /api/v1/widgets' do | |
it 'creates widget' do | |
# See http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html | |
# Will generate header like: | |
# Authorization: Token token=<TOKEN_HERE> | |
# but these would also work: | |
# Authorization: Token <TOKEN_HERE> | |
# Authorization: Bearer <TOKEN_HERE> | |
# Authorization: Bearer token=<TOKEN_HERE>, foo=bar | |
encoded_credentials = | |
ActionController::HttpAuthentication::Token.encode_credentials('YOUR_API_TEST_TOKEN') | |
headers = { 'Authorization' => encoded_credentials } | |
params = { name: 'Foo', color: 'red' } | |
expect do | |
post '/api/v1/widgets', params: params, headers: headers, as: :json | |
end.to change { Widget.exists?(params) }.from(false).to(true) | |
expect(response).to have_http_status :created | |
expect(response.content_type).to eq 'application/json' | |
# If you need to get the created widget: | |
# widget = Widget.last | |
# expect(widget.confirmed).to eq false | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment