HTTP Token Access Authentication
Ruby gem to handle the HTTP Token Access Authentication draft specification, for securing HTTP-based service and microservice architectures using token credentials. It supports both parsing and building a HTTP Authentication
request header and a WWW-Authenticate
response header using the token scheme.
Rather than being a complete opinionated authentication solution that only works with Rails or a specific HTTP framework, this library aims to be minimalistic and unobtrusive. This allows more flexibility and makes it compatible with virtually any HTTP servers and clients that run on the Ruby platform.
This library does not authenticate users nor provide methods for obtaining token credentials. For that you can use another protocol, such as OAuth, which is implemented by the Ruby OAuth gem.
The following authentication methods are supported:
Works with Ruby >= 2.1.0
.
WARNING: HTTP Token Access Authentication is still a draft specification and may change in the future.
Motivation
I created this library to help authenticate HTTP-based microservices and RESTful APIs in Ruby using token credentials.
Most user-facing applications need to authenticate their users before granting access to protected functionality and unlocking certain areas of the application. Service and microservice oriented architectures typically require an authentication service, responsible for validating user credentials such as e-mail and password. The application then sends user credentials to the authentication service using a secure protocol, such as OAuth. If authentication is successful, the authentication service will return a set of token credentials, which can be used to unlock other services in order to serve a set of features for the end user.
When receiving a HTTP request with a set token credentials, a service first needs to check if they are valid. That might include asking the authentication service if the token identifier is correct and was not expired. If credentials are valid, the service carries on with the request as expected. Otherwise, the request is denied with a 401 Unauthorized
status code.
The following sequence diagram illustrates the steps that need to happen for a successful token access authentication. In this example, an user-facing application needs to display private photos to its end user. First, it authenticates the user credentials using OAuth (it could use any other method for that). Then, in order to retrieve those photos, it makes a request to the photo service, which is the service responsible for the user photos domain. It also attaches to the request an Authorization
request header with the token credentials. The photo service then validates those credentials before handling over the requested photos.
If an unadvertised client makes a HTTP request to the photo service without providing valid token credentials, service is denied. The server sends a 401 Unauthorized
response with a WWW-Authenticate
header containing the authentication challenge, which instructs the client on how to acquire token credentials.
To protect itself against brute force or DoS attacks, the server should also throttle or reject consecutive requests with invalid token credentials coming from the same host.
Please keep in mind that the specification for Token Access Authentication does not define a method for authenticating users nor obtaining token credentials. It simply specifies how to transport and validate existing token credentials.
Background
From the specification:
The HTTP Basic and Digest Access authentication schemes defined by RFC 2617 enable clients to make authenticated HTTP requests by using a username (or userid) and a password. In most cases, the client uses a single set of credentials to access all the resources it controls which are hosted by the server.
While the Basic and Digest schemes can be used to send credentials other than a username and password, their wide deployment and well-established behavior in user-agents preclude them from being used with other classes of credentials. Extending these schemes to support new classes would require an impractical change to their existing deployment.
The Token Access Authentication scheme provides a method for making authenticated HTTP requests using a token - an identifier used to denote an access grant with specific scope, duration, cryptographic properties, and other attributes. Tokens can be issued by the server, self-issued by the client, or issued by a third-party.
The token scheme supports an extensible set of credential classes, authentication methods (e.g. cryptographic algorithm), and authentication coverage (the elements of the HTTP request - such as the request URI or entity-body - covered by the authentication).
Token Access Authentication without a Cryptographic Algorithm
The following HTTP request:
GET /resource/1 HTTP/1.1
Host: example.com
returns the following authentication challenge:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Token realm="http://example.com/",
coverage="none"
This response means the server is expecting the client to authenticate using the token scheme, with a set of token credentials issued for the http://example.com/
realm. The none
coverage method means the server does not employ a cryptographic algorithm and does not provide any security on its own. Servers utilizing this method use the token identifier as a bearer token, relying solely on the value of the token identifier to authenticate the client.
The client then uses another method to obtain the token credentials for accessing resources in the http://example.com/
realm. In this example, the token identifier issued to the client is h480djs93hd8
, and a new HTTP request is attempted:
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Token token="h480djs93hd8"
Since this is a valid token, the request is authenticated and the server carries it on.
WARNING: Without a cryptographic algorithm, Token Access Authentication is insecure and vulnerable to man-in-the-middle attacks. This can be prevented by using HTTPS, which means transmitting HTTP through SSL/TLS encrypted TCP sockets, thus protecting the exchange of secrets and making sure no impostors are faking the server along the way.
Token Access Authentication with a Cryptographic Algorithm
The following HTTP request:
GET /resource/1 HTTP/1.1
Host: example.com
returns the following authentication challenge:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Token realm="http://example.com/",
coverage="base base+body-sha-256",
timestamp="137131190"
The response means the server is expecting the client to authenticate using the token scheme, with a set of token credentials issued for the http://example.com/
realm. The server supports the base
and base+body-sha-256
coverage methods which means the client must sign the base request components (e.g. host, port, request URI), and may also sign the request payload (entity-body). It also provides its current time to assist the client in synchronizing its clock with the server's clock for the purpose of producing a unique nonce value (used with some of the authentication methods).
The client has previously obtained a set of token credentials for accessing resources in the http://example.com/
realm. The credentials issued to the client by the server included the following attributes:
- token:
h480djs93hd8
- method:
hmac-sha-1
- secret:
489dks293j39
- expiration:
137217600
The client attempts the HTTP request again, this time using the token credentials issued by the server earlier to authenticate. The client uses the base
coverage method and applies the hmac-sha-1
authentication method as dictated by the token credentials.
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Token token="h480djs93hd8",
coverage="base",
timestamp="137131200",
nonce="dj83hs9s",
auth="djosJKDKJSD8743243/jdk33klY="
The server then authenticates the request and carries on as expected.
The following cryptographic authentication methods are defined in the specification:
Usage
Parsing an Authorization
Header
require 'http/token_auth'
header = <<-HEADER
Token token="h480djs93hd8",
coverage="base",
timestamp="137131200",
nonce="dj83hs9s",
auth="djosJKDKJSD8743243/jdk33klY="
HEADER
credentials = HTTP::TokenAuth.parse_authorization_header(header)
credentials.token # "h480djs93hd8"
credentials.coverage # :base
credentials.timestamp # 137131200
credentials.nonce # "dj83hs9s"
credentials.auth # "djosJKDKJSD8743243/jdk33klY="
Building an Authorization
Header
Without a Cryptographic Algorithm
A nil coverage
parameter translates to coverage="none"
when building the header. However, the specification states that if coverage is none
, then it can be ommited from the header string.
If coverage is "none", the nonce
, auth
and timestamp
attributes are not used and should not be included in the header.
require 'http/token_auth'
credentials = HTTP::TokenAuth::Credentials.new token: 'h480djs93hd8',
coverage: :none
credentials.to_header
# Token token="h480djs93hd8"
With a Cryptographic Algorithm
A cryptographic algorithm is used if coverage is set to :basic
or :base_body_sha_256
, which translates to the coverage="base"
and coverage="base+body-sha-256"
header attributes, respectively.
In this case, it is mandatory to specify the values of the nonce
, auth
and timestamp
attributes. Details on how to fill those attributes depend on the cryptographic algorithm being used and can be found in the specification.
require 'http/token_auth'
credentials = HTTP::TokenAuth::Credentials.new token: 'h480djs93hd8',
coverage: :base_body_sha_256,
nonce: 'dj83hs9s',
auth: 'djosJKDKJSD8743243/jdk33klY=',
timestamp: 137131200
credentials.to_header
# Token token="h480djs93hd8",
# coverage="base+body-sha-256",
# nonce="dj83hs9s",
# auth="djosJKDKJSD8743243/jdk33klY=",
# timestamp="137131200"
Parsing a WWW-Authenticate
Header
require 'http/token_auth'
header = <<-EOS
Token realm="http://example.com",
coverage="base base+body-sha-256",
timestamp="137131200"
EOS
challenge = HTTP::TokenAuth.parse_www_authenticate_header(header)
challenge.realm # "http://example.com"
challenge.supported_coverages # [:base, :base_body_sha_256]
challenge.timestamp # 137131200
Building a WWW-Authenticate
Header
Without a Cryptographic Algorithm
To create an authentication challenge without support for a cryptographic algorithm, pass :none
as an element of the list supported_coverages
. If not set, this list defaults to "base" as dictated by the specification, which requires a cryptographic algorithm.
It's also mandatory to specify the realm
attribute with the authentication realm URI.
require 'http/token_auth'
challenge = HTTP::TokenAuth::Challenge.new realm: 'http://example.com',
supported_coverages: [:none]
challenge.to_header
# Token realm="http://example.com",
# coverage="none"
With a Cryptographic Algorithm
To create an authentication challenge with support for a cryptographic algorithm, you can pass :base
, :base_body_sha256
or both as elements of the list supported_coverages
. It's also mandatory to specify the realm
attribute with the authenticaiton realm URI and the timestamp
attribute with the server's Unix timestamp.
require 'http/token_auth'
challenge = HTTP::TokenAuth::Challenge.new realm: 'http://example.com',
supported_coverages: [:base, :base_body_sha_256],
timestamp: 137131200
challenge.to_header
# Token realm="http://example.com",
# coverage="base base+body-sha-256",
# timestamp="137131200"
Installation
Add this line to your application's Gemfile:
gem 'http-token-auth'
And then execute:
$ bundle
Or install it yourself as:
$ gem install http-token-auth
Development
Install required gems:
$ bundle install
Running rspec tests and checking for rubocop violations:
$ bundle exec rake
Contributing
Bug reports and pull requests are welcome on GitHub. Before contributing, please read the contributing guidelines.
Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
License
This software is released under the mighty MIT License.