🔥 AdonisJS From Scracth Early Access is Now Open!

Remember me

Implementing a "remember me" feature would greatly enhance the user experience by eliminating the need for users to log in every time they visit. This feature automatically logs users in after their session expires. It works by generating a cryptographically secure token and saving it as a cookie in the user's browser.

Creating the remember me tokens table

The remember me tokens will be stored in a new table called remember_me_tokens, which we need to create:

terminal
node ace make:migration remember_me_tokens

Once created, we will modify the migration as below:

database/migrations/TIMESTAMP_create_remember_me_tokens_table.ts
import { BaseSchema } from '@adonisjs/lucid/schema'

export default class extends BaseSchema {
  protected tableName = 'remember_me_tokens'

  async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments()
      table
        .integer('tokenable_id')
        .notNullable()
        .unsigned()
        .references('id')
        .inTable('users')
        .onDelete('CASCADE')
      table.string('hash').notNullable().unique()
      table.timestamp('created_at').notNullable()
      table.timestamp('updated_at').notNullable()
      table.timestamp('expires_at').notNullable()
    })
  }

  async down() {
    this.schema.dropTable(this.tableName)
  }
}

The remember_me_tokens table will consist of six columns. The key columns are:

  • tokenable_id: This column stores the ID of the user associated with the remember me token.
  • hash: This column contains the hash of the token.
  • expires_at: This column indicates when the token will expire, based on the setting defined as rememberMeTokensAge.

In the down method of the migration, we will simply drop the table.

Finally, we need to run the migration:

terminal
node ace migration:run

We should now have a remember_me_tokens table in the database.

Enabling remember me tokens

To use the "Remember Me" feature, we first need to enable it in config/auth.ts. We do this by setting useRememberMeTokens to true, which indicates that we want to enable remember me tokens. After that, we need to specify the duration for which these tokens will remain valid.

config/auth.ts
const authConfig = defineConfig({
  default: 'web',
  guards: {
    web: sessionGuard({
      useRememberMeTokens: true,
      rememberMeTokensAge: '2 years',
      provider: sessionUserProvider({
        model: () => import('#models/user'),
      }),
    }),
  },
})

Next, we must configure the tokens provider, which will be responsible for reading and writing the tokens. This can be accomplished by defining a static property on the User model:

app/models/user.ts
import { DbRememberMeTokensProvider } from '@adonisjs/auth/session' // [!code]

export default class User extends compose(BaseModel, AuthFinder) {
  ...

  static rememberMeTokens = DbRememberMeTokensProvider.forModel(User) // [!code]
}

Adding the remember me checkbox

We don’t need to remember every user who logs in, only those who choose to be remembered. To achieve this, we will add a checkbox that users can tick to indicate they want to be remembered. This checkbox will be placed after the password field:

resources/views/pages/auth/login.edge
...

<form>
  ...
   
  <div class="flex items-center gap-x-3">
    <input id="remember" name="remember" type="checkbox" class="text-neutral-800 bg-neutral-50 border-neutral-300 rounded focus:border-neutral-300 focus:ring-neutral-800 disabled:cursor-not-allowed disabled:opacity-75">

    <label for="remember" class="text-sm">Remember me</label>
  </div>

  ...
</form>

..
Remember me field
Remember me field

Remembering users

Once we have everything set up, we can implement the functionality to remember users during login. Let’s modify the store method in the AuthController:

app/controllers/auth/auth_controller.ts
async store({ request, auth, session, response }: HttpContext) {
  try {
    const { email, password, remember } = request.only(['email', 'password', 'remember']) 

    const user = await User.verifyCredentials(email, password)

    await auth.use('web').login(user, !!remember) 

    session.flash({
      notification: {
        type: 'success',
        message: 'Welcome back!',
      },
    })

    return response.redirect().toRoute('home')
  } catch (error) {
    session.flash({
      notification: {
        type: 'error',
        message: 'Invalid credentials.',
      },
    })

    return response.redirect().back()
  }
}

In addition to the email and password, we also obtain the "remember" value from the form data. The login method accepts an optional second argument, which is used to create a "remember me" token. This token allows users to be automatically logged in when their session expires. Therefore, we pass the "remember" value as the second argument to the method. This second argument must be a boolean, so we need to cast the value using !! to ensure it is a boolean. If the user checks the "remember me" box, the value of "remember" will be true; otherwise, it will be false.

Whenever the user’s session expires, AdonisJS will use the "remember me" cookie to verify the token's validity and automatically recreate the logged-in session for the user.

Remember user during login
Remember user during login