Skip to content

Support HMAC password hash format from Laravel 12.45.0+#578

Merged
taylorotwell merged 1 commit intolaravel:4.xfrom
ams-ryanolson:fix-authenticate-session-hmac
Jan 6, 2026
Merged

Support HMAC password hash format from Laravel 12.45.0+#578
taylorotwell merged 1 commit intolaravel:4.xfrom
ams-ryanolson:fix-authenticate-session-hmac

Conversation

@ams-ryanolson
Copy link
Contributor

Summary

Laravel Framework v12.45.0 (PR laravel/framework#58107) changed how password hashes are stored in sessions - they're now stored as HMACs instead of raw hashes for improved security.

This updates Sanctum's AuthenticateSession middleware to match the framework's approach.

Changes

  1. storePasswordHashInSession() - Uses hashPasswordForCookie() when available (Laravel 12.45.0+), falls back to raw hash for older versions
  2. validatePasswordHash() - New method that tries HMAC format first, falls back to raw hash comparison for backward compatibility

Problem

When both $middleware->authenticateSessions() and Sanctum stateful auth are enabled:

  1. Login via web route → Laravel's AuthenticateSession stores password_hash_web as HMAC
  2. API request → Sanctum's AuthenticateSession compares against raw hash → mismatch → 401

Backward Compatibility

  • Uses method_exists() checks so it works with Laravel versions before and after 12.45.0
  • Validation tries HMAC first, falls back to raw hash, so existing sessions continue to work

Related

Laravel Framework v12.45.0 (PR laravel/framework#58107) changed how
password hashes are stored in sessions - they're now stored as HMACs
instead of raw hashes for improved security.

This updates Sanctum's AuthenticateSession middleware to:
1. Use hashPasswordForCookie() when storing the password hash (if available)
2. Add validatePasswordHash() that tries HMAC format first, falls back to
   raw hash comparison for backward compatibility

This ensures compatibility when both $middleware->authenticateSessions()
and Sanctum stateful auth are enabled together.

Fixes laravel#577
@rdehnhardt
Copy link

Hello,

This PR addresses the HMAC hash compatibility, which is a great improvement for security.

I wanted to share a related issue and a helpful workaround for users implementing passwordless authentication (such as magic links or OTP), where the user record in the database has a null or empty password field.

In a passwordless setup, you might still encounter issues with AuthenticateSession when it attempts to validate the session state for an authenticated user who is intentionally passwordless.

To prevent potential errors (like a TypeError or unexpected hash comparison failure), users can implement a custom middleware that explicitly bypasses the session validation for authenticated users whose password is null or an empty string.

Here is the custom middleware to use instead of the default Laravel\Sanctum\Http\Middleware\AuthenticateSession:

<?php

// ...

final class AuthenticateSession extends SanctumAuthenticateSession
{
    /**
     * Handle an incoming request.
     *
     * @param  Request  $request
     * @param  Closure(Request): Response  $next
     * @return Response
     * @throws AuthenticationException
     */
    public function handle(Request $request, Closure $next): Response
    {
        if ($this->shouldSkipAuthentication($request)) {
            return $next($request);
        }

        return parent::handle($request, $next);
    }

    /**
     * Determine if the authentication should be skipped.
     *
     * Skip when the user is authenticated but has no password (passwordless users).
     */
    private function shouldSkipAuthentication(Request $request): bool
    {
        $user = $request->user();

        if ($user === null) {
            return false;
        }

        return $user->getAuthPassword() === null || $user->getAuthPassword() === '';
    }
}

This ensures that session validation is correctly skipped when the authenticated user is intentionally passwordless.

* @param string $storedValue
* @return bool
*/
protected function validatePasswordHash(SessionGuard $guard, string $passwordHash, string $storedValue): bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$passwordHash may be null here.

Laravel\Sanctum\Http\Middleware\AuthenticateSession::validatePasswordHash(): Argument #2 ($passwordHash) must be of type string, null given, called in vendor/laravel/sanctum/src/Http/Middleware/AuthenticateSession.php on line 53

Copy link
Contributor

@patrickomeara patrickomeara Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: My user factory wasn't setting a password so some of my tests were failing with the above error.

As @rdehnhardt mentioned there are cases where password is null for real world users so perhaps the $passwordHash type should be ?string or have the type removed. Both SessionGuard::hashPasswordForCookie() and hash_equals do not enforce the string type.

@taylorotwell I've opened a PR #581 to address this.

In my case I've just set a password in my factory.

@ams-ryanolson
Copy link
Contributor Author

ams-ryanolson commented Jan 11, 2026 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants