## External Authentication API clients authenticate with [JSON Web Tokens](http://jwt.io). PostgREST does not support any other authentication mechanism directly, but they can be built on top. In this demo we will build a system that works with an external authentication server and integrates with a PostgREST server by sharing the same JWT secret. For a better understanding of JWT and PostgREST authentication system you should read the [User Management](users/) example as well. I'll use a [Rails](http://rubyonrails.org) application using [Devise](https://github.com/plataformatec/devise) just to make the example more concrete, but this could be replicated for any other external authentication system using the same principles. In case Rails is not your cup of tea you can continue reading and just skip the Ruby code samples. I'll also assume the use of JQuery for some client-side code samples for the sake of simplicity. I won't delve into Devise authentication details, for this would require a tutorial on its own, so I'm assuming that the reader's authentication system is already working. ### Sharing the JWT Secret Allowing a third party to generate valid JWTs for your PostgREST API is just a matter of sharing a secret. So you need to give your authenticator software the same secret that was used in your API server under the ```--jwt-secret``` parameter. This could be done easly using environment variables. You set a ```JWT_SECRET``` variable in the environment where you run your rails app and it will be accessible in the global variable ```ENV['JWT_SECRET']```. ### User Model We will map each user in this example to two database roles. So our application users are either ```admin``` or ```customer```. If they are just visitors (not logged in) to our website they will be ```anonymous```. One way of mapping users is to add a field in our users table indicating their database role. I'll add a text field called role to my users table: ```sql ALTER TABLE users ADD role text NOT NULL DEFAULT 'customer'; ``` Besides the main user that PostgREST uses to connect to PostgreSQL and the anonymous user, we will need two aditional roles for our example: * admin - to be used by users that access all the system rows. * customer - to be used when user has restricted access to database rows. Bellow we have the commands to create all roles that will be used: ```sql CREATE USER authenticator NOINHERIT; CREATE ROLE anonymous; CREATE ROLE admin; CREATE ROLE customer; GRANT customer, admin, anonymous TO authenticator; ``` ### Generating a JWT Several libraries are available to generate JWT, you will find a very handy list in [their website](http://jwt.io) under **Libraries**. To continue our Rails example I'll use the ruby library [json_web_token](https://github.com/garyf/json_web_token). In order to make the gem available in my Rails project I add the following line to my Gemfile: ``` gem 'json_web_token' ``` Then we create a Rails controller to serve JWTs for my authenticated users. For this I just open a file ```app/controllers/api_tokens_controller.rb``` with the content: ```ruby class ApiTokensController < ApplicationController TOKEN_TTL = 1.hour def show unless ENV['JWT_SECRET'].present? return render json: {error: "you need to have JWT_SECRET configured to get an API token"}, status: 500 end unless current_user.present? return render json: {error: "only authenticated users can request the API token"}, status: 401 end expires_in TOKEN_TTL, public: false render json: {token: jwt}, status: 200 end private def jwt JsonWebToken.sign(claims, key: ENV['JWT_SECRET']) end def claims # This token will expire 1 hour after being issued { role: current_user.role, user_id: current_user.id.to_s, exp: (Time.now + TOKEN_TTL).to_i } end end ```
Token Time to Live
In the code above we leverage the HTTP time based cache headers to expire the
endpoint cache at the same time as the token. In this example we have a token
that will be refresh one hour after its issuing time.
That's why both are based on the TOKEN_TTL constant.
DRY priviledge checking conditions
You can encapsulate conditions that will be commonly used to check for privileges while reading a database row.
We used a function current_user_id() but we could add more conditions to functions
as the system becomes more complex.
Remeber to mark your functions as STABLE so that PostgreSQL can inline then while planning the query.