Introduction And Context

Welcome back! In the previous lesson, you learned how to use mock OAuth providers to simulate real-world authentication flows, and you explored how to link and unlink accounts securely. You also saw how important it is to have strong security practices, such as requiring confirmation before linking accounts and preventing duplicate links. These features help keep user accounts safe and flexible.

Now, let's take the next step in building a robust authentication system. In modern web applications, JWTs (JSON Web Tokens) are a popular way to securely manage user authentication and authorization. JWTs allow your server to issue a signed token to the client after a successful login, which the client can then use to access protected resources. This approach is especially important in OAuth systems, where users might sign in using different providers, such as Google or GitHub.

Before diving in, let's clarify what makes JWTs useful. Unlike session-based authentication, where the server stores session data in memory or a database, JWTs are self-contained tokens that include all necessary user information. The client stores the JWT and sends it with each request, and the server can verify it without looking up session data. This makes JWTs scalable and well-suited for distributed systems and APIs.

In this lesson, you will learn how to integrate JWT tokens into your OAuth system to manage authentication in a unified and secure way. By the end, you will understand how to generate JWTs for different authentication methods, verify them in middleware, and enforce permissions based on the user's authentication context. This will prepare you for hands-on practice and more advanced OAuth integrations.

Generating Unified JWT Tokens

Let's start by looking at how JWT tokens are created in a unified authentication system. The generateTokenWithAuthMethod method is responsible for generating a JWT that contains all the information needed to identify the user and their authentication method.

Here is the method:

At the top of the JwtUtil class, we use the auth0 library's JWT functionality, which provides the tools we need to create and verify JWTs. The JWT.create() method returns a builder that lets you add claims (data) to the token. The sign() method takes an algorithm that uses a secret key (stored securely on your server) to sign the token. This creates a cryptographically signed token that proves the payload hasn't been tampered with. The algorithm is configured in the constructor using Algorithm.HMAC256(secret), which implements the HMAC-SHA256 signing algorithm.

When you call this method, it creates a token that includes the user's ID and the authentication method (such as password, , or ). The token's expiry time is set based on the authentication method — password tokens last for one day, while OAuth tokens last for seven days. The method also adds standard JWT claims like (issued at) and (expires at), plus custom claims for the issuer and audience to identify where the token comes from and who it's intended for.

Deep Dive Into The Unified Authentication Verification

Once a JWT token is generated and sent to the client, you need a way to verify it on every request to protected endpoints. This is where the verifyToken method comes in. Its job is to check the Authorization header for a valid JWT, decode it, and load the user's information.

Here's how the method works:

When a request comes in, the method checks for a Bearer token in the header. If the token is missing or invalid, it returns an AuthOutcome with an error response. If the token is valid, it uses jwt.verify(token) to decode and validate the token signature. It then extracts the userId and authMethod claims from the decoded token and fetches the user from the database using the repository's method.

Permission Determination And Checking

Not all users should have the same permissions. For example, an admin might be able to delete any resource, while a regular user can only delete their own. The determinePermissions method decides what permissions a user has based on their role and authentication method.

The User model includes a Role enum (with values like USER and ADMIN) that defines user roles within the system. The permission system uses these roles to determine what actions users can perform.

Here's how it works:

If a user logs in with OAuth, they get extra permissions like oauth_linked. GitHub users specifically get github_linked permissions. If the user is an admin, they get admin and delete_any permissions. Regular users get delete_own.

To enforce these permissions in your controllers, you can check if the current user has the required permission before allowing access to a route. For example:

Using JWT To Check Authentication Status

Now that you have a way to generate and verify JWTs, you can use them to build useful endpoints for your application. The /status endpoint lets clients check the current authentication status, providing detailed information about the user, their permissions, and capabilities.

Here's how the /status endpoint works:

When a client calls this endpoint with a valid token, they get a response showing their user info, authentication method, permissions, and token details. The method uses verifyToken(authorization) to extract and verify the JWT, which automatically handles authentication errors. For example:

This endpoint is particularly useful for client applications that need to display user information, show or hide features based on permissions, or determine when a token is about to expire. The use of makes it easy to construct the JSON response structure in a type-safe way.

Refreshing JWT Tokens To Maintain Sessions

The /refresh endpoint allows clients to get a new token before the old one expires. This is useful for keeping users logged in without making them sign in again. Here's how it works:

The endpoint uses verifyToken(authorization) to verify the current token and extract the authentication context. It then calls generateTokenWithAuthMethod to create a new token with the same authentication method. The expiry time is determined by checking if the user authenticated via OAuth or password. The try-catch block handles any errors that might occur during token generation, ensuring the client receives a clear error message if something goes wrong.

For example, after calling /refresh, the response might be:

This endpoint is very useful in real-world applications where clients need to keep their sessions active. By refreshing tokens before they expire, you can provide a seamless user experience without forcing users to log in repeatedly.

Summary And Preparation For Hands-On Practice

In this lesson, you learned how to integrate JWT tokens into your OAuth authentication system. You saw how to generate unified tokens for different authentication methods, verify them using a verification method, and extract authentication context from each request. You also explored how to determine and enforce permissions, and how to use JWTs with endpoints that report authentication status and refresh tokens.

These concepts are essential for building secure and flexible authentication systems in modern web applications. You are now ready to put these ideas into practice in the CodeSignal IDE. In the next exercises, you will work directly with the code to generate tokens, protect routes, and manage permissions. Great job making it this far — these skills will serve you well as you continue to build advanced OAuth integrations!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal