README.md
# OpenID token proxy
[![Gem Version](https://img.shields.io/gem/v/openid-token-proxy.svg?style=flat)](https://rubygems.org/gems/openid-token-proxy)
[![Build Status](https://img.shields.io/travis/hyperoslo/openid-token-proxy.svg?style=flat)](https://travis-ci.org/hyperoslo/openid-token-proxy)
[![Dependency Status](https://img.shields.io/gemnasium/hyperoslo/openid-token-proxy.svg?style=flat)](https://gemnasium.com/hyperoslo/openid-token-proxy)
[![Code Climate](https://img.shields.io/codeclimate/github/hyperoslo/openid-token-proxy.svg?style=flat)](https://codeclimate.com/github/hyperoslo/openid-token-proxy)
[![Coverage Status](https://img.shields.io/coveralls/hyperoslo/openid-token-proxy.svg?style=flat)](https://coveralls.io/r/hyperoslo/openid-token-proxy)
Retrieves and refreshes OpenID tokens on behalf of a user when dealing with complex
authentication schemes, such as client-side certificates.
**Supported Ruby versions: 2.0.0 or higher**
Licensed under the **MIT** license, see LICENSE for more information.
## Background
When using [OpenID](http://openid.net/specs/openid-connect-core-1_0.html) in
native applications, the most common approach is to open the identity provider's
authorization page in a web view, let the user authenticate and have the application
hold on to access, identity and refresh tokens.
![Regular OpenID flow](docs/regular-openid-flow.png?raw=1)
However, the above flow may be unusable if the identity provider provides complex
authentication schemes, such as client-side certificates.
On iOS, client-side certificates stored in the system keychain [cannot be obtained due to application sandboxing](http://stackoverflow.com/questions/7648487/how-to-list-certificates-from-the-iphone-keychain-inside-my-app).
On Android, one can obtain system certificates but these [can not be used within a web view](http://stackoverflow.com/questions/15588851/android-webview-with-client-certificate).
![OpenID token proxy flow](docs/openid-token-proxy-flow.png?raw=1)
When using OpenID token proxy, the application opens a web browser - which has
access to client-side certificates regardless of storage location - and lets the
user authenticate. The identity provider redirects to the OpenID token proxy,
which in turn passes along any obtained tokens to the application.
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'openid-token-proxy'
```
Or install it yourself:
$ gem install openid-token-proxy
## Usage
### Configuration
```ruby
OpenIDTokenProxy.configure do |config|
config.client_id = 'xxx'
config.client_secret = 'xxx'
config.issuer = 'https://login.windows.net/common'
config.redirect_uri = 'https://example.com/auth/callback'
config.resource = 'https://graph.windows.net'
# By default, only tokens issued for the resource above are accepted
# Alternatively, you can override the allowed audiences or allow multiple:
config.allowed_audiences = [
'https://id.hyper.no',
'https://graph.windows.net'
]
# Indicates which domain users will presumably be signing in with
config.domain_hint = 'example.com'
# Whether to force authentication in case a session is already established
config.prompt = 'login'
# If these endpoints or public keys are not configured explicitly, they will be
# discovered automatically by contacting the issuer (see above)
config.authorization_endpoint = 'https://login.windows.net/common/oauth2/authorize'
config.token_endpoint = 'https://login.windows.net/common/oauth2/token'
config.userinfo_endpoint = 'https://login.windows.net/common/openid/userinfo'
config.public_keys = [
OpenSSL::PKey::RSA.new("-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9...")
]
# Alternatively, you can override the authorization URI in its entirety:
config.authorization_uri = 'https://id.hyper.no/authorize?prompt=login'
end
```
Alternatively, these environment variables will be picked up automatically:
- `OPENID_ALLOWED_AUDIENCES` (comma-separated, defaults to `OPENID_RESOURCE`)
- `OPENID_AUTHORIZATION_ENDPOINT`
- `OPENID_AUTHORIZATION_URI`
- `OPENID_CLIENT_ID`
- `OPENID_CLIENT_SECRET`
- `OPENID_DOMAIN_HINT`
- `OPENID_ISSUER`
- `OPENID_PROMPT`
- `OPENID_REDIRECT_URI`
- `OPENID_RESOURCE`
- `OPENID_TOKEN_ENDPOINT`
- `OPENID_USERINFO_ENDPOINT`
### Token acquirement
OpenID token proxy's main task is to obtain tokens on behalf of users. To allow it
to do so, start by mounting the engine in your Rails application:
```ruby
Rails.application.routes.draw do
mount OpenIDTokenProxy::Engine, at: '/auth'
end
```
Next, register the engine's callback - `https://example.com/auth/callback` - as
the redirect URL of your OpenID application on the issuer so that any authorization
requests are routed back to your application.
The proxy itself also needs to be configured with a redirect URL in order for it
to know what to do with any newly obtained tokens. To boot back into a native
applicaton one could use custom URL schemes or intents:
```ruby
OpenIDTokenProxy.configure do |config|
config.token_acquirement_hook = proc { |token|
"my-app://?token=#{token}&refresh_token=#{token.refresh_token}"
}
end
```
**Warning**: Redirecting to any path with query parameters (e.g. `example.com/?token=xxx`) could theoretically leak tokens to third parties through the `Referer`-header for external assets.
### Token authentication
Additionally, OpenID token proxy ships with an authentication module simplifying
token validation for use in APIs:
```ruby
class AccountsController < ApplicationController
include OpenIDTokenProxy::Token::Authentication
require_valid_token
...
end
```
Access tokens may be provided with one of the following:
- `X-Token` header.
- `Authorization: Bearer <token>` header.
- Query string parameter `token`.
- Cookie `token`.
Token expiry time will be exposed through the `X-Token-Expiry-Time` header.
#### Identity / claims
A valid token is exposed to a controller as `current_token` and identity information
can be extracted by providing a claim name through hash-syntax:
```ruby
current_token['email']
```
Identity providers may support additional claims beyond the [standard OpenID ones](http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims).
### Token refreshing
Most identity providers issue access tokens [with short lifespans](http://openid.net/specs/openid-connect-core-1_0.html#TokenLifetime).
To prevent users from having to authenticate often, refresh tokens are used to
obtain new access tokens without user intervention.
OpenID token proxy's token refresh module does just that:
```ruby
class AccountsController < ApplicationController
include OpenIDTokenProxy::Token::Authentication
include OpenIDTokenProxy::Token::Refresh
require_valid_token
...
end
```
Refresh tokens may be provided with one of the following:
- `X-Refresh-Token` header.
- Query string parameter `refresh_token`.
- Cookie `refresh_token`.
Whenever an access token has expired and a refresh token is given, the module will
attempt to obtain a new token transparently.
The following headers will be present on the API response if, **and only if**, a new
token was obtained:
- `X-Token` header containing the new access token to be used in future requests.
- `X-Refresh-Token` header containing the new refresh token.
You may configure some code to be run (scoped to a controller) when a token is
successfully refreshed:
```ruby
OpenIDTokenProxy.configure do |config|
config.token_refreshment_hook = proc { |token|
cookies[:token] = token.access_token
}
end
```
## Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a pull request
## Credits
Hyper made this. We're a digital communications agency with a passion for good code,
and if you're using this library we probably [want to hire you](http://hyper.no/jobs).