Introduction
Dans cet article je vais vous montrer comment setup une application Adonisjs version 6 et créer un système d’authentification par token OAT (Opaque Access Token).
Avant de commencer le tutoriel je vous invite à lire cette partie de la documentation pour savoir si c’est vraiment ce type d’authentification qui est le mieux pour votre application.
Voici le lien du repository qui contient le code du tutoriel, n’hésitez surtout pas à me donner une ⭐ ça m’aide vraiment !
🇬🇧 English version available here.
Installation
Avant toute chose vérifiez que vous utilisez bien node v22 ou au moins node v20.6.
node -v
# v22.x.x
Il existe 3 starter kit officiel différent pour ne pas commencer à développer son application à partir de rien.
Il y a le slim qui contient juste le core du framework et la structure par défaut des fichiers et dossier d’Adonisjs.
Puis viens le kit web qui vient avec plein de package d’Adonisjs comme lucid qui est l’orm du framework ou encore un template engine nommé edge. Le kit web est une bonne base pour créer une application qui va rendre des vues en html ou Alpine.js par exemple.
Pour finir le troisième kit et donc celui qu’on va utiliser c’est le kit api. Il permet de créer facilement des API qui rendent du JSON.
Ci-dessous la commande pour l’installer. Ici pour le flag -K
nous allons choisir api
et nous allons spécifier que nous voulons des token OAT avec ce flag: --auth-guard=access_tokens
npm init adonisjs@latest -- -K=api --auth-guard=access_tokens
Après avoir rentré les différentes informations que vous demande le CLI et être aller dans le nouveau dossier créé nous pouvons passer à la suite.
Migrations
Durant l’installation du kit on a pu voir que lucid, l’ORM d’Adonisjs a été configuré automatiquement pour utiliser sqlite comme base de donnée. Pour des raisons pratique nous allons garder cette base de donnée pour le reste du tutoriel. Le package d’authentification a lui aussi été configuré pour utiliser les OAT donc rien à faire de notre côté à ce niveau là.
Si on fait un node ace migration:status
on peut voir que le kit nous a créé automatiquement deux migrations: une pour les token et une pour l’utilisateur. Il nous reste plus qu’à migrer la base de donnée avec la commande suivante.
node ace migration:run
Controller
Une fois que les tables ont été migrés il est temps de créer notre controller pour l’authentification et on lui donne le nom “auth”.
node ace make:controller auth
Le nouveau controller est situé dans app/controller
Création de la route register
Pour commencer nous allons créer la route register qui va nous permettre d’enregistrer des utilisateurs dans notre application.
import type { HttpContext } from '@adonisjs/core/http'
import { registerValidator } from '#validators/auth'
import User from '#models/user'
export default class AuthController {
async register({ request, response }: HttpContext) {
const payload = await request.validateUsing(registerValidator)
const user = await User.create(payload)
return response.created(user)
}
}
Pour valider les données qui vont nous être transmise nous allons créer un validator.
node ace make:validator auth
Rendez-vous dans app/validator/auth.ts
On va définir un objet qui aura 3 propriétés:
fullName qui devra avoir une longueur minimum de 3 caractère et maximum 64 et qui devra être de type string.
Email qui devra être de type string et d’être un email :) et qui surtout devra être unique dans la base de donnée.
Password, de type string avec une longueur minimum de 12 caractères jusqu’à 512.
import vine from '@vinejs/vine'
export const registerValidator = vine.compile(
vine.object({
fullName: vine.string().minLength(3).maxLength(64),
email: vine
.string()
.email()
.unique(async (query, field) => {
const user = await query.from('users').where('email', field).first()
return !user
}),
password: vine.string().minLength(12).maxLength(512),
})
)
Il nous reste une dernière étape avant de tester notre route register, rendez-vous dans start/routes.ts
pour créer la route à partir de notre controller.
On va remplacer le code existant par le code ci-dessous:
import router from '@adonisjs/core/services/router'
const AuthController = () => import('#controllers/auth_controller')
router.group(() => {
router.post('register', [AuthController, 'register'])
}).prefix('user')
On va importer notre controller et on va créer une route POST qui est lié à la méthode register de notre controller.
On peut voir que j’ai créé un router group avec un préfixe user, toutes les routes qu’on va mettre dans ce groupe auront pour préfixe user ça veut dire que notre route register n’a pas pour url /register
mais /user/register
.
Lancez le serveur de développement avec cette commande: node ace serve
puis testez la route http://localhost:3333/user/register avec Postman ou un autre outil.
Exemple de body:
{
"fullName": "Maxime",
"email": "max@ime.test",
"name": "Maxime",
"password": "12345678"
}
Si tout se passe bien le serveur devrait vous renvoyer un 201 created avec les infos du user.
Pour éviter que le serveur vous renvoie le hash du password quand vous lui demander de renvoyer l’utilisateur allez dans app/models/users.ts
et rajoutez { serializeAs: null } dans l’argument du décorateur column de password.
@column({ serializeAs: null })
declare password: string
Maintenant quand le serveur renvoie l’utilisateur il va changer la valeur du champ password par la valeur null
. N’hésitez pas à aller ici pour en apprendre plus.
Création de la route login
On va rajouter un validator à notre fichier:
import vine from '@vinejs/vine'
// nouveau validator
export const loginValidator = vine.compile(
vine.object({
email: vine.string().email(),
password: vine.string().minLength(8).maxLength(32),
})
)
export const registerValidator = vine.compile(
vine.object({
fullName: vine.string().minLength(3).maxLength(64),
email: vine
.string()
.email()
.unique(async (db, value) => {
const user = await db.from('users').where('email', value).first()
return !user
}),
password: vine.string().minLength(12).maxLength(512),
})
)
Ainsi que une méthode login pour notre controller:
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
import { registerValidator, loginValidator } from '#validators/auth'
export default class AuthController {
// nouvelle méthode login
async login({ request, response }: HttpContext) {
const { email, password } = await request.validateUsing(loginValidator)
const user = await User.verifyCredentials(email, password)
const token = await User.accessTokens.create(user)
return response.ok({
token: token,
...user.serialize(),
})
}
async register({ request, response }: HttpContext) {
const payload = await request.validateUsing(registerValidator)
const user = await User.create(payload)
return response.created(user)
}
}
Et bien sûr rajouter la route login à notre router:
import router from '@adonisjs/core/services/router'
const AuthController = () => import('#controllers/auth_controller')
router.group(() => {
router.post('register', [AuthController, 'register'])
router.post('login', [AuthController, 'login'])
}).prefix('user')
Quand on teste la route elle nous renvoie l’utilisateur ainsi que le token.
Création d’une route protégée par token
Maintenant que nous avons nos routes login et register nous allons créer une route seulement accessible par un utilisateur enregistré.
Allez dans config/auth.ts
et rajoutez accessToken:
const authConfig = defineConfig({
default: 'api',
guards: {
api: tokensGuard({
provider: tokensUserProvider({
tokens: 'accessTokens', // <---- rajoutez cette ligne
model: () => import('#models/user'),
}),
}),
},
})
Sans ça Adonisjs n’est pas en mesure de trouver notre propriété accessToken de notre modèle et donc vérifier si notre token est bon.
Pour finir ajoutez la route /me
à notre router pour recevoir les informations de l’utilisateur connecté.
import router from '@adonisjs/core/services/router'
import { middleware } from './kernel.js'
const AuthController = () => import('#controllers/auth_controller')
router.group(() => {
router.post('register', [AuthController, 'register'])
router.post('login', [AuthController, 'login'])
}).prefix('user')
// ajoutez cette route
router.get('me', async ({ auth, response }) => {
try {
const user = auth.getUserOrFail()
return response.ok(user)
} catch (error) {
return response.unauthorized({ error: 'User not found' })
}
})
.use(middleware.auth())
La route va renvoyer les informations sur l’utilisateur. On voit qu’on a défini un middleware avec le guard api et qu’on a utiliser l’objet auth pour avoir accès à l’utilisateur. Pour tester la route mettez dans les headers la propriété Authorization la valeur du token (Bearer token).
Création de la route logout
Maintenant que nous avons nos routes pour se login et se register il nous manque plus que la route pour se déconnecter. Pour déconnecter un utilisateur nous devons simplement supprimer son token dans la base de donnée.
On va rajouter une méthode logout à notre controller d’authentification:
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
import { registerValidator, loginValidator } from '#validators/auth'
export default class AuthController {
async login({ request, response }: HttpContext) {
const { email, password } = await request.validateUsing(loginValidator)
const user = await User.verifyCredentials(email, password)
const token = await User.accessTokens.create(user)
return response.ok({
token: token,
...user.serialize(),
})
}
async register({ request, response }: HttpContext) {
const payload = await request.validateUsing(registerValidator)
const user = await User.create(payload)
return response.created(user)
}
// Notre nouvelle route logout
async logout({ auth, response }: HttpContext) {
const user = auth.getUserOrFail()
const token = auth.user?.currentAccessToken.identifier
if (!token) {
return response.badRequest({ message: 'Token not found' })
}
await User.accessTokens.delete(user, token)
return response.ok({ message: 'Logged out' })
}
}
L’objet auth va nous permettre de récupérer l’utilisateur connecté puis on va récupérer son token. Ensuite va vérifier que ce token est pas indéfini (ça ne devrait pas arriver vu que le user s’est authentifié avec) puis on va supprimer le token et renvoyer au client que tout s’est bien passé.
Rajout de la route logout:
router.group(() => {
router.post('register', [AuthController, 'register'])
router.post('login', [AuthController, 'login'])
// Notre nouvelle route logout
router.post('logout', [AuthController, 'logout']).use(middleware.auth())
}).prefix('user')
Comme pour la route me
on va lui dire d’utiliser le middleware d’authentification avec use(middleware.auth())
pour avoir accès à l’objet auth dans notre méthode de notre controller.
Si vous voulez le code complet je vous invite a aller voir mon repository github.
Conclusion
Vous savez maintenant comment créer un système d’authentification par OAT avec Adonisjs. N’hésitez pas à lire la documentation officielle pour en apprendre d’avantage.