JWT Auth with Lucky Api
Tags: jwt, auth, lucky, crystal
Published: 2018-07-08 10:22:47

---
title: JWT Auth with Lucky Api
published: true
description: JWT authentication with Lucky Api
tags: jwt, auth, lucky, crystal
---

Although Lucky is fantastic for building complete applications, I like to build my front-end in Angular so I usually use Lucky as a JSON Api. I prefer it over Rails Api because of the type checking, separation of models from forms and queries, and the way actions and routes are organized.

One feature I usually need in a JSON api is authentication, and today we'll go over setting up JWT authentication with Lucky Api.

## Starter App

To start we'll be using the lucky api demo app which has `User` and `Post` models defined. Run:

```bash
git clone git@github.com:mikeeus/lucky_api_demo.git
git checkout jwt-auth-0
bin/setup
```

You can follow along by switching to the branches shown under the headings for each section. Or look at the finished product by checking out *jwt-auth-10-complete*

## Dependencies
**branch**: *jwt-auth-0*

The only dependecy we'll need is a shard for jwt encoding and decoding. We can use [the crystal jwt package](https://github.com/crystal-community/jwt) so lets add the following to our shard.yml and run `shards`.
```
dependencies:
  jwt:
    github: crystal-community/jwt
```

## Begin with Tests 
**branch**: *jwt-auth-01-sign-in-test*

How else would we know the app is working? Aight, in `spec/blog_api_spec.cr` we'll add a describe block for authentication and our first test which will be for signing in.

**Note** I got `AppVisitor` from [Hannes Kaeufler's blog](https://github.com/hanneskaeufler/blog) which is a great Lucky site that I use as reference.

```ruby
# spec/blog_api_spec.cr
require "./spec_helper"

describe App do
  visitor = AppVisitor.new
  ...
  describe "auth" do
    it "signs in valid user" do
      # create a user
      
      # visit sign in endpoint
      
      # check response has status: 200 and authorization header with "Bearer"
    end
  end
end
```

We'll user Lucky's boxes to make generating test data easy. We'll also use Authentic's `generate_encrypted_password` method to generate our password.

```ruby
# spec/support/boxes/user_box.cr
class UserBox < LuckyRecord::Box
  def initialize
    name "Mikias"
    email "hello@mikias.net"
    encrypted_password Authentic.generate_encrypted_password("password")
  end
end
```

Now we can generate a user in our test and make a post request to our sign_in endpoint using its email and password. And we'll check the response for the correct status code and Authorization header.

```ruby
# spec/blog_api_spec.cr
...
    it "signs in valid user" do
      # create a user
      user = UserBox.new.create
      
      # visit sign in endpoint
      visitor.post("/sign_in", ({
        "sign_in:email" => user.email,
        "sign_in:password" => "password"
      }))
      
      # check response has status: 200 and authorization header with "Bearer"
      visitor.response.status_code.should eq 200
      visitor.response.headers["Authorization"].should_not be_nil
    end
...
```

Now this test will fail because we don't have an action for this route or the forms to handle user creation, so let's build them.

## Sign In
**branch**: *jwt-auth-02-sign-in-form*

If we generate a normal Lucky app it will come with [Authentic](https://github.com/luckyframework/authentic) already configured and several forms and actions will be generated for us. Currently, Lucky api configures `Authentic` but doesn't generate these files so we'll need to add them ourselves and update them to fit our use case.

### Form

Let's start with the `SignInForm` which will be used to validate the user credentials, generate a token and return it in the `Authorization` header of the response. This form will be the same as the one generated by `Authentic` in new non-api apps, and we'll also need to create the form mixin `FindAuthenticable` which wasn't generated.

```ruby
# src/forms/mixins/find_authenticable.cr
module FindAuthenticatable
  private def find_authenticatable
    email.value.try do |value|
      UserQuery.new.email(value).first?
    end
  end
end

# src/forms/sign_in_form.cr
class SignInForm < LuckyRecord::VirtualForm
  include Authentic::FormHelpers
  include FindAuthenticatable

  virtual email : String
  virtual password : String

  private def validate(user : User?)
    if user
      unless Authentic.correct_password?(user, password.value.to_s)
        password.add_error "is wrong"
      end
    else
      email.add_error "is not in our system"
    end
  end
end
```

### Action
**branch**: *jwt-auth-03-complete-sign-in*

Following Lucky's conventions we're going to create two actions:

```bash
lucky gen.action.api SignIn::Create
```

These commands will generate classes at `src/actions/sign_up/create.cr` and `src/actions/sign_in/create.cr` and two post routes to `/sign_up` and `/sign_in`. 

Now we'll need a way to generate tokens from our user, we'll put this method in a `GenerateToken` mixin because we'll use it in several of our actions.

```ruby
# src/actions/mixins/auth/generate_token.cr
require "jwt"

module GenerateToken
  def generate_token(user)
    exp = 14.days.from_now.epoch
    data = ({ id: user.id, name: user.name, email: user.email }).to_s
    payload = { "sub" => user.id, "user" => Base64.encode(data), "exp" => exp }

    JWT.encode(payload, Lucky::Server.settings.secret_key_base, "HS256")
  end
end
```

We also need to make our `User` `PasswordAuthenticatable` for it to be used with `Authentic`. Optionally you can include `Carbon::Emailable` and the `emailable` method if you plan to send emails to your users on registration, password reset, etc. 

```ruby
# src/models/user.cr
class User < BaseModel
  include Carbon::Emailable
  include Authentic::PasswordAuthenticatable

  table :users do
    column name : String
    column email : String
    column encrypted_password : String
  end

  def emailable
    Carbon::Address.new(email)
  end
end
```

Now we can include `GenerateToken` in our `SignIn` action and use our `SignInForm` to complete the authentication.

```ruby
# src/actions/auth/sign_in.cr
class SignIn::Create < ApiAction
  include GenerateToken

  route do
    SignInForm.new(params).submit do |form, user|
      if user
        context.response.headers.add "Authorization", generate_token(user)
        head 200
      else
        head 401
      end
    end
  end
end
```

Run the specs with `crystal spec` and voila! It works! :)

## Sign Up
**branch**: *jwt-auth-04-sign-up-test*

I don't allow sign ups on my blog so I return `head 401` for my `SignIn` action but of course you may want to implement it in yours. It's going to be very similar to the `SignIn` feature with some slight differences. Let's get to it.

### Test

Let's begin by writing a test to create a user, making sure it returns the `Authorization` header and that we can query our new user from the database.

```ruby
# spec/blog_api_spec.cr
describe App do
  ...
  describe "auth" do
    ...
    it "creates user on sign up" do
      visitor.post("/sign_up", ({
        "sign_up:name" => "New User",
        "sign_up:email" => "test@email.com",
        "sign_up:password" => "password",
        "sign_up:password_confirmation" => "password"
      }))

      visitor.response.status_code.should eq 200
      visitor.response.headers["Authorization"].should_not be_nil

      UserQuery.new.email("test@email.com").first.should_not be_nil
    end
  ...
end
```

### Form
**branch**: *jwt-auth-05-sign-up-form*

Now our `SignUpForm` will need a `PasswordValidations` module to check the passwords, we'll create that first.

```ruby
# src/forms/mixins/password_validations.cr
module PasswordValidations
  private def run_password_validations
    validate_required password, password_confirmation
    validate_confirmation_of password, with: password_confirmation
    validate_size_of password, min: 6
  end
end
```

With that we can build our sign up form.

```ruby
# src/forms/sign_up_form.cr
class SignUpForm < User::BaseForm
  include PasswordValidations

  fillable name, email
  virtual password : String
  virtual password_confirmation : String

  def prepare
    validate_uniqueness_of email
    run_password_validations
    Authentic.copy_and_encrypt password, to: encrypted_password
  end
end
```

### Action
**branch**: *jwt-auth-06-complete-sign-up*

With those two things done our we can create our `SignUp::Create` action which will look exactly the same as our `SignIn::Create` action. Run `lucky gen.action.api SignUp::Create` and fill it in:

```ruby
# src/actions/sign_up/create.cr
class SignUp::Create < ApiAction
  include GenerateToken

  route do
    SignUpForm.create(params) do |form, user|
      if user
        context.response.headers.add "Authorization", generate_token(user)
        head 200
      else
        head 401
      end
    end
  end
end
```

Now we can run our tests and watch them pass!

## Protecting Routes

Great we can sign in and sign out, but what good does that do us if we can't protect our resources based on it? Since every action in lucky inerits from `ApiAction` or `BrowserAction`, it's very straight forward to build our own `AuthenticatedAction` that handles getting the current user from the `Authorization` header and returning `head 401` if it's not valid.

### Test
**branch**: *jwt-auth-07-authenticated-action-test*

First let's write test to make sure our feature works as expected. Since we are creating posts with this api, lets make sure that the endpoint is protected. We'll create two specs and update an older one that will be effected by our changes.

Make sure to include the `GenerateToken` module in our specs so we can mock an authenticated request.

```ruby
# spec/blog_api_spec.cr
describe App do
  include GenerateToken
  ...
  describe "/posts" do
    ...
    it "creates post" do
      user = UserBox.create

      visitor.post("/posts",
                   new_post_data,
                   { "Authorization" => generate_token(user) })

      visitor.response_body["title"].should eq "New Post"
    end
  end
  ...
  describe "auth" do
    ...
    it "allows authenticated users to create posts" do
      user = UserBox.create

      visitor.post("/posts",
                   new_post_data,
                   { "Authorization" => generate_token(user) })

      visitor.response_body["title"].should eq new_post_data["post:title"]
    end

    it "rejects unauthenticated requests to protected actions" do
      visitor.post("/posts", new_post_data)
      visitor.response.status_code.should eq 401
    end
  end
end
```

Now our tests will definitely be failing so lets build our `AuthenticatedAction` to make them pass.

### AuthenticatedAction

In order to do so we'll need a way to get the user from the token, so lets create a mixin called `UserFromToken` to do just that.

**Note** I chose to use mixins for generating and parsing tokens but you can also include these methods directly in the user model.

```ruby
# src/actions/mixins/user_from_token.cr
module UserFromToken
  def user_from_token(token : String)
    payload, _header = JWT.decode(token, Lucky::Server.settings.secret_key_base, "HS256")
    UserQuery.new.find(payload["sub"].to_s)
  end
end
```

Now we can use that in our `AuthenticatedAction` class.

```ruby
# src/actions/authenticated_action.cr
abstract class AuthenticatedAction < ApiAction
  include UserFromToken

  before require_current_user

  getter current_user : User? = nil
  
  private def require_current_user
    token = context.request.headers["Authorization"]?

    if token.nil?
      head 401
    else
      @current_user = user_from_token(token)
    end

    if @current_user.nil?
      head 401
    else
      continue
    end
  rescue JWT::ExpiredSignatureError
    head 401
  end

  def current_user
    @current_user.not_nil!    
  end
end
```

So what's happening here? We use a callback `before` to run `require_current_user` before the action is called. In that method we get the user from the `Authorization` token and set it to the `current_user` getter. If there is no token, if the user doesn't exist or if the token is expired (raises `JWT::ExpiredSignatureError`) we return `401`.

We also add a `current_user` method to alias our nilable getter for convenience in our actions.

### Protect Actions
**branch**: *jwt-auth-09-complete-authenticated-action*

Now we can use it in our `Posts::Create` action.

```ruby
class Posts::Create < AuthenticatedAction # changed this
  route do
    post = PostForm.create!(params, author: current_user) # and this
    json Posts::ShowSerializer.new(post), Status::Created
  end
end
```

Now we can run our specs... and BOOM! Protected.

That's whats up.

## Join Us

I hope you enjoyed this tutorial and found it useful. Join us on the [Lucky gitter channel](https://gitter.im/luckyframework/Lobby) to stay up to date on the framework or [checkout the docs](https://luckyframework.org/guides) for more information on how to bring your app idea to life with Lucky.