Authentication is the process of confirming the identity of a user, typically through credentials like a username and a password.
Authorization, determines if a user is permitted to perform an action, once their identity is authenticated.
JWT or JSON Web Token plays a crucial role in authentication and authorization process. It is an efficient and scalable solution for implementing secure authentication and authorization mechanisms in web applications. Learn more here: JWT.
This article demonstrates a simple way to implement authentication using JWT with TypeScript and fastify. For the database, I have Postgres and Prisma as the ORM(Object Relational Mapper) tool.
Getting Started
Follow the instructions provided in the github repository to setup the project: auth-demo.
Highlights
It's always import to store passwords by encrypting them before writing them to the database. I have done it using bcrypt
and a simple salt value of 10 without any algorithm to hash the user password, during registration.
// src/modules/user/user.controller.ts
const newUser = await prismaClient.user.create({
data: {
username,
password: await bcrypt.hash(password, 10),
},
});
How to check the password during login? Since the salt value is private to the program, for any user request, the user can be authenticated by comparing the password with the encrypted password using bcrypt
as well:
// src/modules/user/user.controller.ts
// Compare the password
const isValidPassword = await bcrypt.compare(password, user.password);
It's surprisingly quite simple to do a minimal secure authentication with JWT using fastify-jwt
plugin. This code in index.ts
registers the fastify-jwt
plugin with secret
. This secret
is used to sign and verify the JWTs. Ideally, for the most secure option you would generate a secret using an asymmetric algorithms like RS256, ES256 etc.
// src/index.ts
app.register(fastifyJwt, {
secret: process.env.JWT_SECRET || "supersecret",
});
To generate a JWT token, once a valid user request for login comes through, the JWT toke is generate using a payload. In this program the payload is the user id and the username. You don't want to put the password in the payload because JWTs can be decoded.
// Generate a token
const payload = {
id: user.id,
username: user.username,
};
const token = request.server.jwt.sign(payload);
validTokens.add(token); // Store the token in the in-memory store
I setup an in-memory store for valid tokens using a HashSet for simplicity because it's a backend service. Ideally, would be managed on the client side. It enables the functionality of logging out, because of removing the appropriate token from the store. In user.controller.ts
:
// src/modules/user/user.controller.ts
// In memory store to store the refresh tokens
const validTokens = new Set<string>();
.
.
.
// Remove the token from the in-memory store to logout user
validTokens.delete(token);
To verify if the user is authenticated, it's a simple call from the FastifyRequest
in the controller:
// src/modules/user/user.controller.ts
await request.jwtVerify(); // Verify the JWT token
Additionally, I added further checks to see if the authorization header is present along with token when authenticating user as well as during logging out for more control.
Further Thoughts
Using JWT tokens provides a basic security to secure API endpoints, however with current trends in security, OAuth 2.0 and MFA is the standard.
Storing tokens in-memory for logout functionality is a simple workaround for the backend. In practice, it would be the up to the frontend client application to manage and store JWT tokens to maintain User session.
Conclusion
Authentication need not be complex for getting a proof of concept. With in-demand skills like Typescript and a simple framework like Fastify, you can streamline the process. This article provides a foundation for authentication in beginner applications.
Top comments (0)