Lesson 3
Introduction to JWTs: Authenticate and Access Protected Endpoints
Introduction to JWTs: Authenticate and Access Protected Endpoints

Welcome to this lesson on JWT Authentication. Building on what we learned about session-based authentication, where the server retained user sessions, we now delve into a more stateless form of authentication using JSON Web Tokens (JWTs). JWT authentication is popular because it offers a decentralized and scalable approach, which is crucial for modern applications needing to handle numerous requests efficiently. By the end of this lesson, you will understand how JWTs work and how to use them to securely authenticate API requests, enhancing your capability in managing API interactions. Let's dive into JWT authentication and explore its significance in securing endpoints.

How JWT Works: A Client-Side Perspective

From a client-side perspective, using JWTs involves a series of steps to ensure secure and authenticated interactions with the API. Here’s a simplified breakdown of the JWT process:

  1. User Authentication: The first step is authenticating the user through a login request. The client sends a POST request with the user’s credentials (username and password) to the API's login endpoint.

  2. Token Issuance: Upon successful authentication, the API responds with two tokens:

    • Access Token: This token is a short-lived credential used to access protected API endpoints. It contains encoded information such as the user ID and expiration timestamp.
    • Refresh Token: This is a longer-lived token designed to obtain a new access token without requiring the user to log in again.
  3. Token Utilization: The client stores these tokens and uses the access token to make requests to protected resources. To do this, the access token is added to the HTTP request headers under the key "Authorization." It is usually included as a "Bearer" token, which simply means the token is presented as proof of authentication. So, the header looks like this: Authorization: Bearer YOUR_ACCESS_TOKEN_HERE. This tells the server that the request is authorized and should be granted access to the protected resource.

  4. Access Token Expiration: Once the access token expires, the client can use the refresh token to request a new access token, maintaining a smooth user experience without frequent logins.

In this unit, we'll focus on the access token, understanding its role in securing requests to protected endpoints. Later in this course, we will delve into managing the refresh token, which plays a critical part in sustaining sessions when access tokens expire. By grasping the client-side workflow of JWTs, you can efficiently manage authenticated API interactions in a secure, scalable manner.

Signup Recap

Before diving into JWT-based authentication, let's revisit the signup process we covered in the previous lesson on session-based authentication. Here, we're registering a user with the API using a simple POST request, adding the user to the system without engaging in authentication yet. This familiar process sets the stage for our JWT authentication exploration. Here's a quick example:

Python
1import requests 2 3# Base URL for the API 4base_url = "http://localhost:8000" 5 6# User signup details 7signup_details = { 8 "username": "testuser", 9 "password": "testpass123" 10} 11 12try: 13 # Send a POST request to the signup endpoint with user details 14 signup_response = requests.post(f"{base_url}/auth/signup", json=signup_details) 15 # Raise an error if the request was unsuccessful 16 signup_response.raise_for_status() 17 # Print a success message and the response content 18 print("Signed up successfully!") 19 print(signup_response.json(), "\n") 20except requests.exceptions.HTTPError as err: 21 error_response = err.response.json().get("error", "No specific error message provided") 22 print(f"An HTTP error occurred: {err}") 23 print(f"Error: {error_response}")

In this code, as before, we're making a POST request to our API's signup endpoint to register a new user. This step mirrors the session-based signup approach where user data is added to the system but doesn't directly engage in authentication processes. With the user now registered, we're ready to proceed to the login step and obtain JWT tokens.

Login for JWT Retrieval

With the user now registered, the next crucial step is to log in with these credentials and begin the JWT retrieval. This process involves sending another POST request to the API:

Python
1# User login details (same as signup details) 2login_details = { 3 "username": "testuser", 4 "password": "testpass123" 5} 6 7try: 8 # Send a POST request to the login endpoint with user details 9 login_response = requests.post(f"{base_url}/auth/login", json=login_details) 10 # Raise an error if the request was unsuccessful 11 login_response.raise_for_status() 12 # Print a success message and the response content 13 print("Logged in successfully!") 14 print(login_response.json(), "\n") 15except requests.exceptions.HTTPError as err: 16 error_response = err.response.json().get("error", "No specific error message provided") 17 print(f"An HTTP error occurred: {err}") 18 print(f"Error: {error_response}")

In this login step, we submit a POST request that includes the user credentials. Unlike the session-based method, which used cookies to maintain sessions, this server returns JWTs that provide secure and efficient access to API resources.

Here's how the output might look like after a successful login:

Plain text
1Logged in successfully! 2{ 3 'access_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0dXNlciIsImlhdCI6MTczNzcyNzM5NSwiZXhwIjoxNzM3NzI4Mjk1fQ.3Rl8910TwYHjUPsjx4ql7XLfSHuzsQzCPYyzPe204po', 4 'refresh_token': '15c8cbda2d99307391c160de1b8f2f687c620dc7626c7f7b80ef10ca72ceab80', 5 'message': 'Login successful' 6}

In this response, the access_token is a JWT utilized for authorizing requests, containing encoded information such as the user ID and token timestamps. The refresh_token, on the other hand, serves as a secure token to renew the access token without requiring the user to log in again. Lastly, the message confirms the successful login and issuance of the tokens.

Extracting Access and Refresh Tokens

Once you've successfully logged in, it's essential to extract the JWT tokens from the login response to use them in your application:

Python
1try: 2 # Extract the access token from the login response 3 access_token = login_response.json().get("access_token") 4 # Extract the refresh token from the login response 5 refresh_token = login_response.json().get("refresh_token") 6except (KeyError, AttributeError) as e: 7 print(f"An error occurred during token extraction: {e}")

In this final code snippet, we pull out the access_token and refresh_token from the server's response. These tokens enable you to authenticate API requests and manage sessions in a stateless manner. The access token facilitates immediate request authentication while the refresh token ensures the continuity of the session by allowing renewal of access tokens without requiring user reauthentication. By efficiently handling these tokens, we embrace a scalable and secure method for managing authenticated interactions with the API.

Making Requests to Protected Endpoints with JWT

With the access token in hand, we can now authenticate requests to protected API endpoints. This is done by including the JWT in the HTTP request headers, ensuring that the API recognizes and allows the request. Here’s how you can make a request to a secured endpoint using the access token:

Python
1# Define the headers with the JWT token for authentication 2headers = { 3 "Authorization": f"Bearer {access_token}" 4} 5 6try: 7 # Access a protected endpoint with the JWT token 8 todos_response = requests.get(f"{base_url}/todos", headers=headers) 9 # Raise an error if the request was unsuccessful 10 todos_response.raise_for_status() 11 # Print a success message and the response content 12 print("Accessed todos successfully!") 13 print(todos_response.json(), "\n") 14except requests.exceptions.HTTPError as err: 15 error_response = err.response.json().get("error", "No specific error message provided") 16 print(f"An HTTP error occurred: {err}") 17 print(f"Error: {error_response}")

In this code, the access token is included in the "Authorization" header as a Bearer token. This header is crucial, as it signals to the API that the request is properly authenticated, granting access to the protected resources. For example, the /todos endpoint in this snippet is accessed securely, and if successful, the protected data is printed in the output.

By making requests in this manner, you ensure that sensitive operations and data retrievals are secure, leveraging the JWT approach for stateless, scalable, and flexible interaction with the API.

Summary and Preparing for Practice

In this lesson, you have enhanced your API authentication skills by understanding and utilizing JWTs. From signing up and logging in to securing requests with tokens, you are now equipped to apply JWT authentication in real-world scenarios. The transition from session-based to token-based authentication brings flexibility, scalability, and ease to API interactions. As you proceed to practice exercises, apply these new techniques, explore different endpoints, and solidify your understanding of JWT authentication. Keep experimenting to master these concepts, preparing you for further advanced methods like refreshing tokens and signing out in subsequent lessons.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.