Testing Authenticated API Endpoints with JavaScript
Testing Authenticated Endpoints
Welcome to the final lesson in our journey through automated API testing with JavaScript. In this lesson, we'll focus on testing authenticated API endpoints using JavaScript tools like fetch for making HTTP requests and jest for writing and running tests. Authentication is essential for securing API endpoints against unauthorized access. By understanding how to test these mechanisms, you'll ensure that your API maintains its integrity and protects sensitive data. We'll explore practical examples for each authentication method — API Keys, Sessions, and JWT (JSON Web Tokens) — giving you the tools to verify that only authorized users can interact with protected resources.
API Key Authentication
API Key authentication is one of the simplest ways to secure an API. It involves sending a unique key as part of the request headers, allowing access to protected endpoints. Let's look at an example of how you can set up and test API Key authentication using JavaScript:
Here, we define a test using jest. We specify the headers, including the X-API-Key, and use the fetch function to make a call to the /todos endpoint, ensuring the API key is included in the request. The test then asserts that the response status code is 200, indicating successful authentication and access to the endpoint.
Session Authentication: Setup
We begin our session-based authentication by establishing a fixture for user credentials and a helper function for login. This enables each test to authenticate independently, providing a consistent environment to test both login functionality and access to protected endpoints:
JavaScript
const fetch = require('node-fetch');const BASE_URL = "http://localhost:8000";const authDetails = { username: "testuser", password: "testpass123"};// Helper function for sending a login POST requestasync function login(authDetails) { // Sends a login request and maintains session state const response = await fetch(`${BASE_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(authDetails) }); return response;}
The authDetails object supplies the required credentials, and the login helper function facilitates independent authentication for each test.
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal
Session Authentication: Testing Login
This test leverages the login helper function to authenticate independently, validating that user credentials create a session successfully:
test('Session Authentication: Login', async () => { // Act - Execute the login process using the helper function const loginResponse = await login(authDetails); // Assert - Verify that the login was successful expect(loginResponse.status).toBe(200);});
The use of the helper ensures that this test does not rely on any previous operations, providing isolated verification of login success via status code.
In this test, we independently authenticate using the helper function before confirming that an established session grants access to a protected endpoint:
test('Session Authentication: Access Protected Endpoint', async () => { // Arrange - Use the login helper function to authenticate await login(authDetails); // Act - Request the protected resource const response = await fetch(`${BASE_URL}/todos`); // Assert - Confirm access is granted with a successful status code expect(response.status).toBe(200);});
The helper function's strategic use ensures that each test individually verifies session-based access, unaffected by the results of prior tests.
Session Authentication: Testing Logout
By using the login helper function, this test independently authenticates and then verifies the ability to terminate a session, ensuring logout functionality is effective:
test('Session Authentication: Logout', async () => { // Arrange - Use the login helper function to authenticate await login(authDetails); // Act - Perform a logout operation to terminate the session const logoutResponse = await fetch(`${BASE_URL}/auth/logout`, { method: 'POST' }); // Assert - Verify logout success via status code expect(logoutResponse.status).toBe(200);});
This approach confirms isolation, as each session is independently managed, ensuring logout integrity without relying on previous tests.
JWT Authentication: Setup
For JWT-based authentication, we replicate this strategy with a fixture for credentials and a login helper function, enabling each test to independently obtain and manage JWTs:
const fetch = require('node-fetch');const BASE_URL = "http://localhost:8000";const authDetails = { username: "testuser", password: "testpass123"};// Helper function for sending a login POST requestasync function login(authDetails) { // Obtain JWT tokens with login request const response = await fetch(`${BASE_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(authDetails) }); return response;}
This setup empowers each test to authenticate independently, retrieving tokens without relying on the order or success of other tests.
JWT Authentication: Testing Login
Using the login helper function, this test independently verifies that successful authentication results in token issuance, forming the basis for accessing protected resources:
test('JWT Authentication: Login', async () => { // Act - Authenticate and retrieve JWT tokens const loginResponse = await login(authDetails); // Assert - Confirm receipt of tokens and check their existence expect(loginResponse.status).toBe(200); const tokens = await loginResponse.json(); expect(tokens).toHaveProperty('access_token'); expect(tokens).toHaveProperty('refresh_token');});
By independently logging in, the test ensures token validity regardless of prior tests, establishing the foundation for secure access.
JWT Authentication: Testing Protected Endpoint
This test independently authenticates using the helper function and verifies that a JWT access token provides entry to a protected endpoint:
test('JWT Authentication: Access Protected Endpoint', async () => { // Arrange - Authenticate and obtain an access token using the helper function const loginResponse = await login(authDetails); const tokens = await loginResponse.json(); const accessToken = tokens.access_token; // Act - Use the access token to request a protected endpoint const headers = { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" }; const response = await fetch(`${BASE_URL}/todos`, { headers }); // Assert - Confirm the request was successful by checking the status code expect(response.status).toBe(200);});
By ensuring the test is independent, this approach validates the token's effectiveness in protecting resources, confirming stateless authentication.
JWT Authentication: Testing Logout
Finally, this test independently authenticates to verify that access and refresh tokens can be invalidated, securing the system against unauthorized reuse:
test('JWT Authentication: Logout', async () => { // Arrange - Authenticate and prepare tokens using the helper function const loginResponse = await login(authDetails); const tokens = await loginResponse.json(); const accessToken = tokens.access_token; const refreshToken = tokens.refresh_token; const headers = { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" }; // Act - Use access and refresh tokens to process logout const logoutResponse = await fetch(`${BASE_URL}/auth/logout`, { method: 'POST', headers, body: JSON.stringify({ refresh_token: refreshToken }) }); // Assert - Verify logout success through confirmation of status code expect(logoutResponse.status).toBe(200);});
This test confirms the secure handling and invalidation of tokens, ensuring each test independently checks the integrity of the logout process.
Summary and Practices
Throughout this lesson, we've explored different methods of API authentication: API Keys, Sessions, and JWTs. Each method has distinct ways to test and secure access, and you've seen how to implement tests for them effectively using JavaScript tools like fetch for HTTP requests and jest for testing.
When testing any form of authentication, it’s crucial to handle API credentials with care. Avoid hardcoding sensitive information in your codebase, and consider using environment variables or secure vaults in production environments.
As we conclude this lesson and the course, take pride in the journey you've completed. You've gained a comprehensive understanding of how to automate API tests using JavaScript, mastering basic requests through to secure, authenticated endpoints. Continue to practice and explore, applying these skills to ensure robust, reliable APIs in your projects. Congratulations on reaching the end of this course!
test('Session Authentication: Login', async () => { // Act - Execute the login process using the helper function const loginResponse = await login(authDetails); // Assert - Verify that the login was successful expect(loginResponse.status).toBe(200);});
JavaScript
test('Session Authentication: Access Protected Endpoint', async () => { // Arrange - Use the login helper function to authenticate await login(authDetails); // Act - Request the protected resource const response = await fetch(`${BASE_URL}/todos`); // Assert - Confirm access is granted with a successful status code expect(response.status).toBe(200);});
JavaScript
test('Session Authentication: Logout', async () => { // Arrange - Use the login helper function to authenticate await login(authDetails); // Act - Perform a logout operation to terminate the session const logoutResponse = await fetch(`${BASE_URL}/auth/logout`, { method: 'POST' }); // Assert - Verify logout success via status code expect(logoutResponse.status).toBe(200);});
JavaScript
const fetch = require('node-fetch');const BASE_URL = "http://localhost:8000";const authDetails = { username: "testuser", password: "testpass123"};// Helper function for sending a login POST requestasync function login(authDetails) { // Obtain JWT tokens with login request const response = await fetch(`${BASE_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(authDetails) }); return response;}
JavaScript
test('JWT Authentication: Login', async () => { // Act - Authenticate and retrieve JWT tokens const loginResponse = await login(authDetails); // Assert - Confirm receipt of tokens and check their existence expect(loginResponse.status).toBe(200); const tokens = await loginResponse.json(); expect(tokens).toHaveProperty('access_token'); expect(tokens).toHaveProperty('refresh_token');});
JavaScript
test('JWT Authentication: Access Protected Endpoint', async () => { // Arrange - Authenticate and obtain an access token using the helper function const loginResponse = await login(authDetails); const tokens = await loginResponse.json(); const accessToken = tokens.access_token; // Act - Use the access token to request a protected endpoint const headers = { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" }; const response = await fetch(`${BASE_URL}/todos`, { headers }); // Assert - Confirm the request was successful by checking the status code expect(response.status).toBe(200);});
JavaScript
test('JWT Authentication: Logout', async () => { // Arrange - Authenticate and prepare tokens using the helper function const loginResponse = await login(authDetails); const tokens = await loginResponse.json(); const accessToken = tokens.access_token; const refreshToken = tokens.refresh_token; const headers = { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" }; // Act - Use access and refresh tokens to process logout const logoutResponse = await fetch(`${BASE_URL}/auth/logout`, { method: 'POST', headers, body: JSON.stringify({ refresh_token: refreshToken }) }); // Assert - Verify logout success through confirmation of status code expect(logoutResponse.status).toBe(200);});