How to build an API with authentication in Adonis

Introduction

Introduction

In this tutorial, we will be building a simple API, and we will add authentication to it. Thanks to Adonis.js this can be handled easily for this awesome framework comes with JWT authentication out of the box. If you don't know what I mean by JWT, you can head over to this link to grasp more info about it. Now it's time to jump into this tutorial. We'll build an API for a blog posts and so only authenticated users can perform some defined operations over posts.

Prerequisites

In order to follow this tutorial, knowledge of JavaScript and Node.js is required. You should also have the following installed on your machine:

Set up the Adonis project

First open your terminal and type this command to install the Adonis CLI and create a new Adonis app:

1# if you don't have Adonis CLI installed on your machine. 
2      npm install -g @adonisjs/cli
3      
4    # Create a new adonis app and move into the app directory
5    $ adonis new adonis_auth-api && cd adonis_auth-api

Now start the server and test if everything is working fine:

1adonis serve --dev
2    
3    2018-09-23T12:25:30.326Z - info: serving app on http://127.0.0.1:3333

Open your browser and make a request to: http://127.0.0.1:3333. You should see the following:

adonis-default

Set up the database and create the migration

Create a database.sqlite file in the database directory, and amend the .env file like this:

1DB_CONNECTION=sqlite
2    DB_DATABASE=/absolute/path/to/database.sqlite
3    DB_HOST=127.0.0.1
4    DB_PORT=3306
5    DB_USER=your_database_user
6    DB_PASSWORD=your_dtabase_password

Open your terminal and run this command to generate our Post model as well as its corresponding controller and migration file which will be used to build the schema of our posts table:

    adonis make:model Post -mc

Inside your post migration file copy and paste this code:

1//../database/migrations/*_.user.js
2    'use strict'
3    
4    const Schema = use('Schema')
5    
6    class PostSchema extends Schema {
7        up() {
8            this.create('posts', (table) => {
9                table.increments()
10                table.string('title')
11                table.string('description')
12                table.integer('user_id').unsigned();
13                table.foreign('user_id').references('Users.id').onDelete('cascade');
14                table.timestamps()
15            })
16        }
17    
18        down() {
19            this.drop('posts')
20        }
21    }
22    
23    module.exports = PostSchema

This code is pretty similar to what we are accustomed to in Laravel migration. You can see we defined our PostSchema table fields as:

  • title
  • price
  • description
  • user_id

The increments() will create an id field with Auto Increment and set it as Primary key. The timestamps() will create the created_at and updated_at fields respectively. We also set a foreign key on the user_id field to ensure that the user submitting post does exist.

Now if you run this command: adonis migration:run in your terminal it will create the posts, and users (which migration is defined by default) tables in your database. We need to define some relations on our models to make things easier for us to handle. Let's say a user can have one or many posts, and a single post belongs to a particular user. Let's translate that into code:

Add this function to your post model

1//../app/Models/Post.js
2        user() {
3            return this.belongsTo('App/Models/User');
4        }

and this one to your user model

1//../app/Models/User.js
2        posts() {
3            return this.hasMany('App/Models/Post')
4        }

This has been very simple to achieve :). We're done with that.

Build our authentication workflow

Our users need to register and to get authenticated in order to perform any sensible operation on our data. When they register, our API will generate an authorization token which he can append to his future requests to manipulate our posts data. Basically we'll build a register and a login function, so we need to create our authentication controller.

First make this change in your auth.js file to tell our app to use jwt as our authenticator.

1//../config/auth.js
2    authenticator: 'jwt',

Then head over to your terminal and type this command to create your controller:

    adonis make:controller --type http AuthController

The flag --type is to specify the type of controller we want to create, in our case it is an HTTP controller. Now copy and paste this code inside your AuthController file.

1//../app/Controllers/Http/AuthController.js
2    
3    'use strict'
4    const User = use('App/Models/User');
5    
6    class AuthController {
7    
8      async register({request, auth, response}) {
9    
10        let user = await User.create(request.all())
11    
12        //generate token for user;
13        let token = await auth.generate(user)
14    
15        Object.assign(user, token)
16    
17        return response.json(user)
18      }
19    
20      async login({request, auth, response}) {
21    
22        let {email, password} = request.all();
23    
24        try {
25          if (await auth.attempt(email, password)) {
26            let user = await User.findBy('email', email)
27            let token = await auth.generate(user)
28    
29            Object.assign(user, token)
30            return response.json(user)
31          }
32    
33    
34        }
35        catch (e) {
36          console.log(e)
37          return response.json({message: 'You are not registered!'})
38        }
39      }
40      async getPosts({request, response}) {
41        let posts = await Post.query().with('user').fetch()
42    
43        return response.json(posts)
44      }
45      
46    }
47    
48    module.exports = AuthController

As expected we have our two functions. The register function creates a new user with the data sent in the request and generates an authorization token for that user, and returns the fresh created user as the response.

Our second function login checks first if the user information is valid, then it grasps all their data and also generate an authorization token for them, and returns the logged in user.

Note that the generated token will be appended to our future requests as I said above to help authenticate the user and help them perform the intended action on our data only if they are authenticated and recognized by our API system.

Now let's set up our routes. Go to your routes.js file and paste the following code inside:

1//../start/routes.js
2    const Route = use('Route')
3    
4    Route.post('/register', 'AuthController.register')
5    Route.post('/login', 'AuthController.login')
6    
7    Route.put('/posts/:id', 'PostController.update').middleware('auth')
8    Route.delete('posts/id', 'PostController.delete').middleware('auth')
9    Route.post('/posts', 'PostController.store').middleware('auth')
10    Route.get('/posts', 'PostController.getPosts');

Our two first routes are for authentication purposes, one for user registration and the other for the user signin. Then we define some routes our users can make requests to in order to manipulate our posts’ data as you can notice :). We add the auth middleware to some our routes that require user authentication because the operation intended is quite sensible, and we need to ensure that the user is authorized by the system to perform that operation.

Define our PostController

This controller will be responsible of handling requests over our posts’ data. In your terminal, type this command to create your controller:

    adonis make:controller --type http PostController

The PostController is now generated. It’s time to define its functions, open the file and paste the following block of code inside the body of your controller class:

1//../app/Controllers/Http/PostController.js
2    'use strict'
3    const Post = use('App/Models/Post');
4    
5    class PostController {
6      async getPosts({request, response}) {
7        let posts = await Post.query().with('user').fetch()
8    
9        return response.json(posts)
10      }
11    
12      async create() {
13      }
14    
15      async store({request, auth, response}) {
16    
17        try {
18          // if (await auth.check()) {
19          let post = await auth.user.posts().create(request.all())
20          await post.load('user');
21          return response.json(post)
22          // }
23    
24        } catch (e) {
25          console.log(e)
26          return response.json({message: 'You are not authorized to perform this action'})
27        }
28    
29      }
30    
31      async update({auth, params, response}) {
32    
33        let post = await Post.find(params.id)
34        post.title = request.input('title')
35        post.description = request.input('description');
36    
37        await post.save()
38        await post.load('user');
39    
40        return response.json(post)
41      }
42    
43      async delete({auth, params, response}) {
44    
45        await Post.find(params.id).delete()
46    
47        return response.json({message: 'Post has been deleted'})
48      }
49    
50    }
51    
52    module.exports = PostController

Let’s explain those four functions defined above:

  • getPosts fetches all posts from database and returns them as the response

  • store pulls the requests data to create a new post, associates the current authenticated user as the author, and returs the fresh created post with its associated user.

  • update updates a post by fetching its id with info pulled from the request’ object and returns it

  • delete at last, finds a post by given its id and deletes from the data, then it returns a message.

Test your API with Postman

Now let's test the API with Postman. If you don't have Postman, you can get it here. No don't thank me, it's free 😉 .

First, you have disable the CSRF protection in order to send API requests without problem. Head over to the shield.js file, and make this change:

1//../config/shield.js
2    ...,
3    csrf: {
4        enable: false,
5       ...
6      }
7    }

If you don’t know at all how to use Postman, head over to this link to get more insights, and then come back to the tutorial.

Say we want to get posts from our database, we type localhost:3000/posts to fetch all our posts and select the GET method on the postman UI, and hit the Send button to send your request. You’ll get your posts if they are or an empty array if the database is empty.

If you want to register to the system, select the POST method and enter your credentials(username,email,password) in the body tab, check them to take them into account, and hit the Send button to send the request. The response should be a user object containing your authentication token.

As you can see in the images below, we've tested some of our routes, let's say the important ones register, login, posts. And you can see in the images below that our API is working fine and token is generated on user registration and login.

adonis-auth-postman-1
adonis-auth-postman-2
adonis-auth-postman-3

Conclusion

Through this tutorial you've learnt how you can build an API with authentication for your users. I hope this has been useful to you and will help you understand how you can integrate this kind of system into an existing application of yours. Feel free to get the source code of the project on GitHub.