This is just some code I recently used in my development application in order to add token-based authentication for my api-only rails app. The api-client was to be consumed by a mobile application, so I needed an authentication solution that would keep the user logged in indefinetly and the only way to do this was either using refresh tokens or sliding sessions.
I also needed a way to both blacklist and whitelist tokens based on a unique identifier (jti)
Before trying it out DIY, I considered using:
- devise-jwt which unfortunately does not support refresh tokens
- devise_token_auth I ran into issues when it came to the changing headers on request on mobile, disabling this meant users would have to sign in periodically
- doorkeeper This was pretty close to what I needed, however, it was quite complicated and I considered it wasn't worth the extra effort of implmeneting OAuth2 (for now)
- api_guard This was great, almost everything I needed but it didn't play too nicely with GraphQL and I needed to implement token whitelisting also.
So, since I couldn't find any widely-used gem to meet my needs; I decided to just go DIY, and the end result works pretty well. And overview of how things works is so:
- You call on the
Jwt::Issuer
module to create anaccess_token
andrefresh_token
pair. - You call on the
Jwt::Authenticator
module to authenticate theaccess_token
get thecurrent_user
and thedecoeded_token
- You call on the
Jwt::Revoker
module to revoke (blacklist/remove whitelist) a token - You call on the
Jwt::Refresher
module to refresh anaccess_token
based on a refresh_token
There are more modules, but you can preview them for yourself.
There are some prequistes you need in order to use this code:
-
You need to create a blacklisted tokens table like so:
rails g model BlacklistedToken jti:string:uniq:index user:belongs_to exp:datetime
-
If you want to use whitelisting to, create a tokens table like so:
rails g model WhitelistedToken jti:string:uniq:index user:belongs_to exp:datetime
-
Create a refresh tokens table like & model so:
rails g model RefreshToken crypted_token:string:uniq user:belongs_to
class RefreshToken < ApplicationRecord
belongs_to :user
before_create :set_crypted_token
attr_accessor :token
def self.find_by_token(token)
crypted_token = Digest::SHA256.hexdigest token
RefreshToken.find_by(crypted_token: crypted_token)
end
private
def set_crypted_token
self.token = SecureRandom.hex
self.crypted_token = Digest::SHA256.hexdigest(token)
end
end
- Update the user model to include the associations
has_many :refresh_tokens, dependent: :delete_all
has_many :whitelisted_tokens, dependent: :delete_all
has_many :blacklisted_tokens, dependent: :delete_all
Then you are pretty much ready to go!
In the future, I might make this into a gem or add redis support or similar.
I hope this gist helps someone!
Thanks for amazing work.
I'm getting
#<NameError: uninitialized constant Jwt::Authenticator::Errors
, how should I define these classes?