How To Make Your AdonisJS Authentication Login Case-Insensitive

Learn how to make the AdonisJS login attempt query case-insensitive easily by adding a simple method to our User Model.

Published
May 28, 22
Duration
6m 0s

Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.

Adocasts

Burlington, KY

By default, AdonisJS Auth will be case-sensitive when checking your user’s UID value during login. This means, that if we allow our user’s to log in with their username, they’ll need to provide the username with the same casing they used when they signed up.

So long as you’re ensuring usernames within your application are case-insensitive unique, we can simplify things for our users by altering the Auth attempt call to be case-insensitive, allowing them to use their username with any casing combination. This comes in handy on mobile, tablets, and odd systems where the keyboard may default to the first character being capitalized.

If you’re new to AdonisJS authentication, you can get caught up in 15 minutes in our “AdonisJS Authentication in 15 Minutes” lesson.

Ensuring Usernames Are Unique Case-Insensitive

First, before we begin, let’s ensure we’re forcing all our usernames to be case-insensitive and unique within our database using the AdonisJS Validator.

const userSchema = schema.create({
  username: schema.string(
    { trim: true }, [
      rules.unique({ 
        table: 'users', 
        column: 'username', 
        caseInsensitive: true // 👈
      })
    ]
  ),
  email: schema.string(
    { trim: true }, [
      rules.email(), 
      rules.unique({ 
        table: 'users', 
        column: 'email', 
        caseInsensitive: true // 👈
      })
    ]
  ),
  password: schema.string({}, [rules.minLength(8)])
})

Also, note that if you have a pre-existing database here, you’ll want to run a query to verify this won’t cause trouble for any existing users.

Making Auth Attempt Case-Insensitive

Now that we’ve verified that this change won’t impact any of our user’s abilities to log in or register, let’s go ahead and implement the ability to log in using a case-insensitive UID.

First, let’s head into our User model at App/Models/User.ts. When authenticating a user, AdonisJS performs a check for a method on our User model, called findForAuth, this check is shown below.

// AdonisJS' search for a findForAuth function
if (typeof model.findForAuth === 'function') {
  const user = await model.findForAuth(this.config.uids, uidValue)
  return this.getUserFor(user)
}

If this method isn’t present on our User model, AdonisJS will perform an orWhere for each UID defined within our config, shown below.

// the AdonisJS default
const { query } = await this.getModelQuery()
this.config.uids.forEach((uid) => query.orWhere(uid, uidValue))
return this.findUser(query)

So, in order to make our UID search case-insensitive, we’ll want to define a method called findForAuth on our User model. This method will be provided the uids array from our config along with the login attempt’s uidValue. then, inside our findForAuth method we’ll want to create a query that’ll find our user.

public static async findForAuth(uids: string[], uidValue: string) {
  return this.query().where(query => 
    uids.map(uid => query.orWhere(uid, 'ILIKE', uidValue))
  ).firstOrFail()
}

By using an ILIKE, we’re telling the query to perform a case-insensitive LIKE check for the uidValue. However, we aren’t adding any % signs, so we’re essentially performing a case-insensitive equality check here which is exactly what we want.

With this in place, AdonisJS will now use the findForAuth function we’ve defined on our User model when attempting to find a user during login. Which, we’ve defined to search for a UID match that’s case-insensitive. Job well done, everyone!

AdonisJS Lucid v18

If you’re running AdonisJS Lucid version 18 or later (the May 2022 release), there’s a new query builder method called whereILike. You can swap the above query.orWhere(uid, 'ILIKE', uidValue) for query.orWhereILike(uid, uidValue) for a cleaner alternative.

TypeScript Is Complaining?

Though the above works just fine, you may run into TypeScript complaining about your findForAuth method. There may be a better way to rectify this, however, I’ve found altering how we attach it to our User model resolves the TypeScript complaint.

First, switch your User model to export default at the end of the file.

class User extends BaseModel {
  // ...

  public static async findForAuth(uids, uidValue) {
    return this.query().where(query => 
      uids.map(uid => query.orWhere(uid, 'ILIKE', uidValue))
    ).firstOrFail()
  }
}

export default User

Then, move your findForAuth method declaration outside of the User model.

class User extends BaseModel {
  // ...
}

User['findForAuth'] = function (uids, uidValue) {
  return this.query().where(query => 
    uids.map(uid => query.orWhere(uid, 'ILIKE', uidValue))
  ).firstOrFail()
}

export default User

With that, TypeScript should no longer be complaining and our application will use a case-insensitive lookup on our user’s UID value when logging in.

Join The Discussion! (0 Comments)

Please sign in or sign up for free to join in on the dicussion.

robot comment bubble

Be the first to Comment!