Back to Blog
Lesson 39 of the Intermediate Laravel: Real-World Application Patterns course
LaravelJune 26, 20264 min read

Testing API Authentication in Laravel with Sanctum

Learn how to rigorously secure your endpoints by testing Sanctum authentication. Master asserting unauthorized codes, token validation, and user mocking.

laraveltestingapiauthenticationsanctumphpunitphpbackend

Previously in this course, we covered the basics of Feature Testing Fundamentals: Automating Laravel API Verification. While those lessons established how to assert JSON responses, they often assumed an open environment. In this lesson, we add the crucial layer of security verification: ensuring your API endpoints are correctly guarded.

Testing API Authentication from First Principles

When we build APIs, our security relies on the assumption that an endpoint is inaccessible unless a valid identity is provided. In Laravel, when using Mastering REST API Authentication with Laravel Sanctum, we aren't just testing if the code runs; we are testing the middleware layer.

Testing authentication involves three distinct scenarios:

  1. The Negative Path: Asserting that an unauthenticated request receives a 401 Unauthorized status code.
  2. The Positive Path: Verifying that a request with a valid Sanctum token is successfully authenticated and can access protected resources.
  3. The Isolated Path: Mocking the authenticated user to test controller logic without needing to hit the database for token generation every time.

Asserting Unauthorized Access

Before testing success, always test failure. If your endpoint doesn't return a 401 for guest users, your security layer is misconfigured.

PHP
#6A9955">/** @test */
public function it_denies_access_to_unauthenticated_users()
{
    #6A9955">// We send a request to a protected route without any headers
    $response = $this->getJson('/api/v1/projects');

    #6A9955">// Assert that the API rejects the request
    $response->assertStatus(401);
}

This test ensures that your auth:sanctum middleware is correctly applied to the route group. If this test fails, you have an unprotected route, regardless of how robust your logic is.

Testing Sanctum Token Authentication

To test that a real token works, we need to create a user and issue a token via Sanctum's createToken method. This is a functional test that verifies the entire integration between the database and the authentication guard.

PHP
#6A9955">/** @test */
public function it_allows_access_to_authenticated_users_with_valid_token()
{
    $user = User::factory()->create();
    $token = $user->createToken('test-token')->plainTextToken;

    $response = $this->withHeader('Authorization', 'Bearer ' . $token)
        ->getJson('/api/v1/projects');

    $response->assertStatus(200);
}

Using withHeader directly is the most explicit way to test the integration. It ensures that the Authorization header is being correctly parsed by the Sanctum guard.

Mocking Authenticated Users

In larger test suites, creating tokens for every single request can become a performance bottleneck. Laravel provides a helper to "act as" a specific user, which bypasses the token verification process entirely. This is ideal for testing authorization logic (like policies) in isolation.

PHP
#6A9955">/** @test */
public function it_can_access_projects_as_a_specific_user()
{
    $user = User::factory()->create();

    #6A9955">// Use actingAs to simulate an already-authenticated state
    $response = $this->actingAs($user, 'sanctum')
        ->getJson('/api/v1/projects');

    $response->assertStatus(200);
}

By passing 'sanctum' as the second argument, you tell Laravel to use the Sanctum guard for this user instance. This is cleaner and faster than manual token generation when you are testing business rules rather than the authentication mechanism itself.

Hands-on Exercise

  1. Identify a protected route: Choose one of the project board endpoints you created in earlier lessons.
  2. Write the negative test: Create a new test class and implement a test method that asserts a 401 status when requesting that route without a token.
  3. Write the positive test: Implement a test that uses actingAs($user, 'sanctum') to verify that the same route returns a 200 status for an authenticated user.
  4. Refactor: If you find your tests are repeating the user creation, move the user factory call to a setUp method or a custom trait.

Common Pitfalls

  • Forgetting the Guard: When using actingAs, always remember to pass 'sanctum' as the second argument. If omitted, Laravel defaults to the web guard, which will fail if your API routes aren't configured to use session authentication.
  • Database State: Ensure your database is migrated and cleaned between tests. If you are using RefreshDatabase, ensure your factories are defined correctly, otherwise, your authentication tests might fail due to missing user records.
  • Testing Middleware vs. Logic: Don't use actingAs if you are specifically testing that your authentication middleware works. Use the header-based approach for verifying the security layer, and actingAs for verifying business logic.

Recap

Testing API authentication is about verifying the boundary between public and private data. We use assertStatus(401) to ensure gateways are locked, withHeader to verify the full token integration, and actingAs to efficiently test authorized workflows. By mastering these three patterns, you ensure your API remains secure as it grows.

Up next: We will dive into Job Chaining and Batching to handle multi-step background processes for our project board.

Similar Posts