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.
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.
Let's start by looking at how JWT tokens are created in a unified authentication system. The generate_unified_token function is responsible for generating a JWT that contains all the information needed to identify the user and their authentication method.
Here is the function:
At the top of the file, we import the jose library's jwt module, which provides the tools we need to create and verify JWTs. The jwt.encode() method takes three arguments: the payload (the data you want to encode), a secret key (used to sign the token so it can be verified later), and the algorithm. This method returns a signed token string that can be sent to the client. The JWT_SECRET_KEY is a secret string stored securely on your server — it should never be exposed to clients. When you call jwt.encode(), it creates a cryptographically signed token that proves the payload hasn't been tampered with.
When you call this function, it creates a payload that includes the user's ID, the authentication method (such as , , or ), and the provider. If the authentication method is not , it also adds a cryptographically secure unique token ID () for extra security. The token's expiry time is set based on the authentication method — password tokens might last for one day, while OAuth tokens could last for a week.
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 get_auth_context function 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 function works:
When a request comes in, the function checks for a Bearer token in the header. If the token is missing or invalid, it raises an HTTPException. If the token is valid, it decodes the payload and fetches the user from the database. It then creates an object, which includes the user, the authentication method, whether the user logged in with OAuth, their permissions, and token details like when it was issued and when it expires.
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 determine_permissions function decides what permissions a user has based on their role and authentication method.
Here's how it works:
If a user logs in with OAuth, they get an extra permission called oauth_linked. If the user is an admin, they get admin and delete_any permissions. Regular users get delete_own.
To enforce these permissions, the require_permission function checks if the current user has the required permission before allowing access to a route. For example, if a route requires admin permission, only users with that permission can proceed.
Here's a simple example:
If a user tries to access a protected route without the right permission, they will see a response like this:
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 what actions they can perform. The function uses await get_auth_context(request, db) to extract and verify the JWT, which automatically handles authentication errors. For example:
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 await get_auth_context(request, db) to verify the current token and extract the authentication context. It then calls generate_unified_token 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-except 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.
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 dependency function, and attach an authentication context to 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!
