DEV Community

Cover image for Social login and authentication in Adonis JS
Raphael Otuya
Raphael Otuya

Posted on

Social login and authentication in Adonis JS

In this tutorial we will be going through user authentication in Node JS specifically Adonis JS.
We will be using social login methods example: sign in with Facebook, Google and GitHub, using an Adonis JS package called Ally.

Let’s get into it.

I will assume you have an Adonis project already set up with lucid or your preferred method for storing information, we will then need to install the following packages:
• Ally
• Auth
Ally is a social login tool for Adonis JS, it has to be installed and configured separately.

Run the following commands to install and configure Ally:

npm i @adonisjs/ally

node ace configure @adonisjs/ally.
Enter fullscreen mode Exit fullscreen mode

You will have to update your “clientId”, “clientSecret” and “callbackUrl” in the configuration file stored in the config/ally.ts directory.
ClientId and clientSecret are gotten from which platform you choose to use ie facebook, Google, Github, while the callbackUrl is the url which you will define to handle the response gotten from the provider.
For this tutorial, i will be using the google provider.

Step 1: make user model and migration.
Using the cli command:

node ace make:model User –m

The “-m” flag creates a migration along with the model.
Add other fields you want to store on the table.

The user migration file:

import BaseSchema from '@ioc:Adonis/Lucid/Schema'

export default class Users extends BaseSchema {
  protected tableName = 'users'

  public async up () {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id');
      table.string('name').notNullable();
      table.string('avatar_url');
      table.string('email').notNullable();
      table.string('provider');
      table.string('provider_id');


      /**
       * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
       */
      table.timestamp('created_at', { useTz: true })
      table.timestamp('updated_at', { useTz: true })
    })
  }

  public async down () {
    this.schema.dropTable(this.tableName)
  }
}
Enter fullscreen mode Exit fullscreen mode

This is pretty self-explanatory, we are creating a table with column:
• Id: auto incrementing counter for the rows
• Name: the name of the user which we will get from the provider
• Avatar_url: user profile picture url, stored as a string
• Email: user email
• Provider: the driver the user used to sign up for our app
• Provider id: a unique id gotten from the provider
The created_at and updated_at are auto generated and will be updated automatically on creation and update of rows ie user.
The user model:

import { DateTime } from 'luxon'
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'

export default class User extends BaseModel {
  @column()
  public id: number

  @column()
  public name: string;

  @column()
  public avatar_url: string;

  @column({isPrimary: true})
  public email: string;

  @column()   
  public providerId: string;

  @column()
  public provider: string;

  @column.dateTime({ autoCreate: true })
  public createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  public updatedAt: DateTime
}

Enter fullscreen mode Exit fullscreen mode

Note that the content of your model should always match your migration.

Step 2: Create signup controller
Use the cli command: node ace make: controller GoogleSignup
A file will be created in the app/controllers directory. Paste the following code in the file:

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User';

export default class GoogleSignInsController {
    public async redirect({ally}: HttpContextContract) {
        return ally.use('google').redirect() 
    }

}
Enter fullscreen mode Exit fullscreen mode

We are creating a method that redirects the user to the OAuth providers website for authentication.

Step 3: Handle callback
Paste the following code in the same file, it includes the method created above.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User';

export default class GoogleSignInsController {
    public async redirect({ally}: HttpContextContract) {
        return ally.use('google').redirect() 
    }

    public async handleCallback ({ally, auth, response}: HttpContextContract) {
        const googleUser = ally.use('google');

        /**
         * User has explicitly denied the login request
         */
        if (googleUser.accessDenied()) {
            return 'Access was denied'
        }

        /**
         * Unable to verify the CSRF state
         */
        if (googleUser.stateMisMatch()) {
            return 'Request expired. try again'
        }

        /**
         * There was an unknown error during the redirect
         */
        if (googleUser.hasError()) {
            return googleUser.getError()
        }

        /**
         * Finally, access the user
         */
        const user = await googleUser.user();

        const findUser = {
            email: user.email as string
        }

        const userDetails = {
            name: user.name as string,
            email: user.email as string,
            avatar_url: user.avatarUrl as string,
            provider_id: user.id as string,
            provider: 'google'
        } 

        const newUser =await User.firstOrCreate(findUser, userDetails);

        await auth.use('web').login(newUser)
        response.status(200);

        return newUser;
    }

}
Enter fullscreen mode Exit fullscreen mode

Here we are handling all the use cases if the sign in failed before we store the user in the database

const user = await googleUser.user(); //stores the user information object gotten back from google
Enter fullscreen mode Exit fullscreen mode
 const newUser =await User.firstOrCreate(findUser, userDetails);
Enter fullscreen mode Exit fullscreen mode

Here, we first query the database using the email of the user which is stored in the findUser object, if the email exists in the database, return the first one, otherwise create a new user with the userDetails object.

await auth.use('web').login(newUser)
Enter fullscreen mode Exit fullscreen mode

Above, we use the built in adonis auth package to log the user in and create a session.

Step 3: Attach controllers to route
In the routes file, we will create a route for calling and initializing the OAuth provider and another route for handling the callback

// SIGN IN ROUTES
Route.get('/google-signin', 'GoogleSignInsController.redirect');

//OAuth CALLBACK
Route.get('/google-signin-callback', 'GoogleSignInsController.handleCallback');

Enter fullscreen mode Exit fullscreen mode

Note that the route above will be the route you input as your callbackUrl in the ally configuration file.

Top comments (0)