0

Multi-auth — multiple guard configurations

Advanced5 min read·lv-16-008
interview

Concept

JWT (JSON Web Token) is a stateless authentication mechanism where all necessary information about the user is encoded in the token itself. The server signs the token — any server with the same secret can verify it without a database lookup.

JWT structure: Three Base64URL-encoded parts separated by dots: header.payload.signature.

  • Header: Algorithm and token type: {"alg":"HS256","typ":"JWT"}.
  • Payload: Claims — registered (sub, iat, exp) and custom (role, user_id).
  • Signature: HMAC(base64(header) + '.' + base64(payload), secret).

Stateless vs session: Sessions store auth state server-side (DB/Redis). JWT embeds state in the token. The server never stores the JWT — just verifies the signature.

Trade-offs:

  • Pro: Scales horizontally without shared session storage. Verify on any server with the secret.
  • Pro: Good for microservices — services can verify tokens independently.
  • Con: Cannot revoke a JWT before expiry (no server-side state to invalidate). Mitigate with short expiry + refresh tokens.
  • Con: Payload is Base64-encoded, NOT encrypted. Don't put sensitive data in JWT payload.
  • Con: Larger than session cookies.

In Laravel: Not built-in. Use tymon/jwt-auth package or implement manually. For most Laravel APIs, use Sanctum — it's simpler and server-managed. JWT shines in microservice/polyglot architectures.

Refresh tokens: Short-lived access token (15m–1h) + long-lived refresh token. Client exchanges refresh token for new access token. Allows revocation of refresh tokens (stored server-side) while keeping access tokens stateless.

Code Example

php
<?php
// Manual JWT implementation (educational — use a package in production)
class JwtService
{
    private string $secret;
    private int $ttl = 3600; // 1 hour

    public function __construct()
    {
        $this->secret = config('app.key');
    }

    public function encode(array $payload): string
    {
        $header  = base64url_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
        $payload = base64url_encode(json_encode(array_merge($payload, [
            'iat' => time(),
            'exp' => time() + $this->ttl,
        ])));
        $sig = base64url_encode(hash_hmac('sha256', "$header.$payload", $this->secret, true));
        return "$header.$payload.$sig";
    }

    public function decode(string $token): array
    {
        [$header, $payload, $sig] = explode('.', $token);
        $expectedSig = base64url_encode(hash_hmac('sha256', "$header.$payload", $this->secret, true));
        if (!hash_equals($expectedSig, $sig)) throw new \Exception('Invalid signature');
        $data = json_decode(base64url_decode($payload), true);
        if ($data['exp'] < time()) throw new \Exception('Token expired');
        return $data;
    }
}

// Using tymon/jwt-auth package
// composer require tymon/jwt-auth
// php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
// php artisan jwt:secret

class AuthController extends Controller
{
    public function login(Request $request)
    {
        $credentials = $request->validate(['email' => 'required|email', 'password' => 'required']);
        $token = auth('api')->attempt($credentials);
        if (!$token) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }
        return response()->json([
            'access_token'  => $token,
            'token_type'    => 'bearer',
            'expires_in'    => auth('api')->factory()->getTTL() * 60,
        ]);
    }

    public function refresh()
    {
        return response()->json([
            'access_token' => auth('api')->refresh(),
        ]);
    }
}