68-Nodejs Course 2023: Auth: Base Auth Model Class
Hasan Zohdy
Posted on November 19, 2022
So we saw how to generate an access token when the guest performs a login, let's see a quick peek at the code.
// src/app/users/controllers/auth/login.ts
import User from "app/users/models/user";
import jwt from "core/auth/jwt";
import { Request } from "core/http/request";
import { Response } from "core/http/response";
export default async function login(request: Request, response: Response) {
// get the email and password from the request body
const { email, password } = request.only(["email", "password"]);
const user = await User.attempt({ email, password });
if (!user) {
return response.badRequest({
error: "Invalid credentials",
});
}
// generate access token
const token = await jwt.generate({
...user.only(["id", "_id"]),
userType: "user",
});
return response.success({
user: user.data,
// send the access token to the client
accessToken: token,
// send the user type to the client
userType: "user",
});
}
We did also make it with guests as well, let's have a look
// src/core/auth/registerAuthRoutes.ts
import { Response } from "core/http/response";
import router from "core/router";
import jwt from "./jwt";
import AccessToken from "./models/access-token";
import Guest from "./models/guest";
export default function registerAuthRoutes() {
// now let's add a guests route in our routes to generate a guest token to our guests.
router.post("/guests", async (_request, response: Response) => {
// generate a new guest first
const guest = await Guest.create({
userType: "guest",
});
// use our own jwt generator to generate a token for the guest
const token = await jwt.generate(guest.data);
AccessToken.create({
token,
// get the guest user type, id and _id
...guest.only(["id", "userType", "_id"]),
});
return response.send({
accessToken: token,
// return the user type
userType: guest.get("userType"),
});
});
}
You'll notice that, we forgot in the login to store the token in AccessToken
model, also there is a duplicate in the code when generating a jwt token.
Let's encapsulate the code with Auth
model class.
// src/core/auth/models/auth.ts
import { Model } from "core/database";
export default abstract class Auth extends Model {
/**
* Get user type
*/
public abstract get userType(): string;
/**
* Generate jwt token
*/
public async generateAccessToken(): Promise<string> {
//
return "";
}
}
Here we used the power of abstract
concept (You can find it in depth in the OOP Course, link in the article tail), to enforce that this class can not be instantiated, and any child must implement a getter method userType
to return the user type of its own.
Then we defined generateAccessToken
method, but it is so far has empty body with an empty string returned, this method should generate an access token, store it in the AccessToken
model, and return that token.
// src/core/auth/models/auth.ts
import { Model } from "core/database";
import jwt from "../jwt";
import AccessToken from "./access-token";
export default abstract class Auth extends Model {
/**
* Get user type
*/
public abstract get userType(): string;
/**
* Generate jwt token
*/
public async generateAccessToken(): Promise<string> {
//
// store the main data in the data object
// we need to store the user data in an object
// that we'll sue to generate the token
// and also it will be saved in the Access Token model under `user` column
const data = {
...this.only(["id", "_id"]),
userType: this.userType,
};
// use our own jwt generator to generate a token for the guest
const token = await jwt.generate(data);
// store token and the auth model data in the access token model
// note that we didn't make it sync because we don't want to wait for the token to be stored in the database
// as nothing depends on it
AccessToken.create({
token,
user: data,
});
return token;
}
}
Here we created an object that collects the main data that we need, the id
, _id
and the userType
, then we used our own jwt generator to generate a token for our current user, after that we stored the token and the auth model data
in the access token model.
We grouped the data under user
field so we don't override the _id
or id
of the AccessToken
collection itself.
Let's now make our Guest
extends Auth
class.
// src/core/auth/models/guest.ts
import Auth from "./auth";
export default class Guest extends Auth {
/**
* {@inheritDoc}
*/
public static collectionName = "guests";
/**
* Get user type
*/
public get userType(): string {
return "guest";
}
}
We extended the Auth
class, and implemented the userType
getter method to return the user type of the guest.
Now let's update our registerAuthRoutes
function to use the new generateAccessToken
method.
// src/core/auth/registerAuthRoutes.ts
import { Response } from "core/http/response";
import router from "core/router";
import Guest from "./models/guest";
export default function registerAuthRoutes() {
// now let's add a guests route in our routes to generate a guest token to our guests.
router.post("/guests", async (_request, response: Response) => {
// generate a new guest first
const guest = await Guest.create({
userType: "guest",
});
// use our own jwt generator to generate a token for the guest
const token = await guest.generateAccessToken();
return response.send({
accessToken: token,
// return the user type
userType: guest.userType,
});
});
}
Now our code is much cleaner and more readable, and we can use the same method in our User
model.
// src/app/users/models/user.ts
import { verify } from "@mongez/password";
import { except } from "@mongez/reinforcements";
import Auth from "core/auth/models/auth";
import castPassword from "core/database/casts/cast-password";
import { Casts, Document } from "core/database/model/types";
export default class User extends Auth {
/**
* Collection name
*/
public static collectionName = "users";
/**
* Get user type
*/
public get userType(): string {
return "user";
}
/**
* {@inheritDoc}
*/
public defaultValue: Document = {
isActive: true,
isEmailVerified: false,
isPhoneVerified: false,
};
protected casts: Casts = {
isActive: "boolean",
isPhoneVerified: "boolean",
joinDate: "date",
password: castPassword,
};
/**
* Attempt to login the user
*/
public static async attempt(data: any) {
// find first user with the given data, but exclude from it the password
const user = await this.first(except(data, ["password"]));
if (!user) {
return null;
}
// now verify the password
if (!verify(user.get("password"), data.password)) {
return null;
}
return user;
}
}
We extended the Auth
class, and implemented the userType
getter method to return the user type of the user.
Actually you know what, the attempt
method can be used with multiple users, so let's move it to the Auth
class.
// src/core/auth/models/auth.ts
// ...
/**
* Attempt to login the user
*/
public static async attempt<T>(
this: ChildModel<T>,
data: any,
): Promise<T | null> {
// find first user with the given data, but exclude from it the password
const user = await this.first(except(data, ["password"]));
if (!user) {
return null;
}
// now verify the password
if (!verify((user as Model).get("password"), data.password)) {
return null;
}
return user;
}
Here we moved the attempt
method to the Auth class, we also as you can remember, did update the definition of this
in the static method so we can return a proper return type when the login attempt is successful.
Now let's update our login route to use the new generateAccessToken
method.
// src/app/users/controllers/auth/login.ts
import User from "app/users/models/user";
import { Request } from "core/http/request";
import { Response } from "core/http/response";
export default async function login(request: Request, response: Response) {
// get the email and password from the request body
const { email, password } = request.only(["email", "password"]);
const user = await User.attempt({ email, password });
if (!user) {
return response.badRequest({
error: "Invalid credentials",
});
}
const token = user.generateAccessToken();
return response.success({
user: user.data,
// send the access token to the client
accessToken: token,
// send the user type to the client
userType: user.userType,
});
}
We did exact same thing here, we used the attempt
method to attempt to login the user, and if the login attempt is successful, we used the generateAccessToken
method to generate a token for the user.
Also don't forget that we return the userType using the
userType
getter method.
🎨 Conclusion
We created an Auth
base model so we can extend from, and we used the generateAccessToken
method to generate a token for the user, and we moved the attempt
method to attempt from the User
model to the Auth
model so we can use it with multiple users.
☕♨️ Buy me a Coffee ♨️☕
If you enjoy my articles and see it useful to you, you may buy me a coffee, it will help me to keep going and keep creating more content.
🚀 Project Repository
You can find the latest updates of this project on Github
😍 Join our community
Join our community on Discord to get help and support (Node Js 2023 Channel).
🎞️ Video Course (Arabic Voice)
If you want to learn this course in video format, you can find it on Youtube, the course is in Arabic language.
📚 Bonus Content 📚
You may have a look at these articles, it will definitely boost your knowledge and productivity.
General Topics
- Event Driven Architecture: A Practical Guide in Javascript
- Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript
- After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
Packages & Libraries
- Collections: Your ultimate Javascript Arrays Manager
- Supportive Is: an elegant utility to check types of values in JavaScript
- Localization: An agnostic i18n package to manage localization in your project
React Js Packages
Courses (Articles)
Posted on November 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.