MortalToken, because some tokens shouldn’t live forever
MortalToken is a convenience wrapper for HMAC-based authentication. The tokens self-destruct after a specified time period; no need to store and look them up for verification.
The default lifespan is one hour. For a Web-based application, you might extend this to many hours, or you might cut it back to only a few minutes, issuing a new token for each request/response cycle.
My original use case was login auth tokens for some simple Sinatra apps. I can also see it being useful for API auth. Of course there are potential uses outside of HTTP, too.
Steps:
-
Generate a new token
-
Give the client the resulting digest, salt, and expiry timestamp
-
Time passes
-
Receive a digest, salt, and expiry timestamp from client
-
Reconstitute the token from salt and timestamp, then see if the digests match. If not, the token has expired (or was forged).
Warning
It is up to you to transmit the digest, salt, and expiry timestamp securely. If it’s intercepted, someone could impersonate your client until the token expires. In other words, this is only part of an authentication solution. Use with caution. May contain traces of peanut.
Install
[sudo] gem install mortal-token
Or add it to your Gemfile
gem "mortal-token"
Example use with Sinatra
require 'sinatra'
require 'mortal-token'
MortalToken.secret = 'asdf092$78roasdjfjfaklmsdadASDFopijf98%2ejA#Df@sdf'
post '/login' do
if login_ok?
token = MortalToken.new
# Or "token = MortalToken.new(current_user.id)" to set your own salt and verify who owns the session.
session[:salt] = token.salt
session[:expires] = token.expires
session[:digest] = token.digest
redirect '/secret'
end
end
get '/secret' do
if MortalToken.check(session[:salt], session[:expires]).against(session[:digest])
'Nice token!'
else
'Your token is expired or forged!'
end
end
Automatically re-issue nearly expired tokens
MortalToken.check(session[:salt], session[:expires]).against(session[:digest]) do |token|
session[:salt], session[:expires], session[:digest] = MortalToken.new.get if token.expires_soon?
end
Checking token validity explained
MortalToken.check(salt, expires).against(digest)
is syntactic sugar for
reconstituted_token = MortalToken.new(salt, expires)
reconstituted_token == digest
A token’s == and === methods accept another token or a digest. In the example above, an attempt has been made to reconstitute the original token using it’s salt and expiry timestamp. To be considered “equal”, the reconstituted token’s digest must match the original digest AND the timestamp must be in the future. Unless both of those conditions are met, then token is considered invalid or expired.
Tweak token parameters
You may tweak certain parameters of the library in order to make it more secure, less, faster, etc. These are the defaults (see the MortalToken class for documentation about each parameter):
MortalToken.valid_for = 1 # tokens are valid for N units
MortalToken.units = :hours # or :days, :minutes
MortalToken.digest = 'sha256' # The digest algorithm used by HMAC
MortalToken.max_salt_length = 50 # Maximum token salt length
MortalToken.min_salt_length = 10 # Minimum token salt length
Multiple configurations
Your application may want to use MortalTokens in various contexts, where the same parameters may not make sense (probably units and valid_for). You may define different scopes and give them each their own config. Always define the default scope first (above). Other scopes will inherit its secret unless you specify another.
MortalToken.config(:foo) do |config|
config.units = :minutes
config.valid_for = 10
end
token = MortalToken.use(:foo).token
License
Copyright 2012 Jordan Hollinger
Licensed under the Apache License