🐘PHP

Integrating JWT Validation in Your Application

This guide walks you through integrating JWT validation within your application, ensuring secure communication with our Authopia services.

Prerequisites

Ensure you have guzzlehttp/guzzle and web-token/jwt-framework installed in your PHP project. If not, you can install them using Composer:

composer require guzzlehttp/guzzle web-token/jwt-framework

Steps for JWT Validation

1. Obtain Your Customer and Project ID

After successful login into the Admin Panel, note down your customer-id and project-id. These are essential for fetching the JWKS and validating tokens specific to your project.

2. Deserialize the JWT

Extract the JWT sent to your backend and deserialize it to access the headers and payload. This step is crucial for subsequent validation checks.

use Jose\Component\Signature\Serializer\CompactSerializer;

$serializer = new CompactSerializer(); 
$jws = $serializer->unserialize($jwtToken); 

3. Perform Header Checks

Ensure the JWT's header contains the necessary parameters like alg and kid for secure processing.

use Jose\Component\Checker\HeaderCheckerManager; 
use Jose\Component\Signature\Algorithm\RS256; 
use Jose\Component\Signature\JWSTokenSupport;

$headerCheckerManager = new HeaderCheckerManager(
    checkers: [new AlgorithmChecker(['RS256'])],
    tokenTypes: [new JWSTokenSupport()],
);
$headerCheckerManager->check(
    jwt: $jws,
    index: 0,
    mandatoryHeaderParameters: ['alg', 'kid'],
);

4. Validate Claims

Use the ClaimCheckerManager to validate standard claims such as issued at (iat), not before (nbf), expiration time (exp), audience (aud), and issuer (iss). Adjust the audience and issuer values according to your application's requirements.

use Jose\Component\Checker\ClaimCheckerManager;
use Jose\Component\Checker\{IssuedAtChecker, NotBeforeChecker, ExpirationTimeChecker, AudienceChecker, IssuerChecker};

$audience = 'YOUR-RP-ID';

$claimCheckerManager = new ClaimCheckerManager(checkers: [
    new IssuedAtChecker(),
    new NotBeforeChecker(),
    new ExpirationTimeChecker(),
    new AudienceChecker(audience: $audience),
    new IssuerChecker(issuers: ['api.authopia.io']),
]);

$claims = json_decode($jws->getPayload(), true);
$claimCheckerManager->check(
    claims: $claims, 
    mandatoryClaims: ['iat', 'nbf', 'exp', 'email', 'projectId']
);

5. Fetch the JWKS and Match the Correct Key

Retrieve the JWKS from Authopia and select the correct key using the kid found in the JWT's header.

Note: To enhance performance and reduce the load on the Authopia servers, implement a caching mechanism to store the JWKS. This way, you don't have to fetch the JWKS for every authentication attempt, only when the cache expires or when the key indicated by the JWT's kid is not found in the cache.

use GuzzleHttp\Client;
use Jose\Component\Core\JWKSet;

$client = new Client();
$response = $client->get(sprintf('https://api.authopia.io/v1/customers/%s/.well-known/jwks.json', $customerId));
$jwks = json_decode($response->getBody()->getContents(), true);
$jwkSet = JWKSet::createFromKeyData($jwks);

$kid = $jws->getSignature(0)->getProtectedHeader()['kid'];
$key = $jwkSet->get($kid);

6. Verify the JWT's Signature

With the correct key in hand, verify the JWT's signature to ensure it was indeed issued by Authopia and has not been tampered with.

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Signature\JWSVerifier;

$algorithmManager = new AlgorithmManager([new RS256()]);
$jwsVerifier = new JWSVerifier($algorithmManager);

$isValid = $jwsVerifier->verifyWithKey($jws, $key, 0);

Verification Outcome

Based on the verification result, take the appropriate action within your application.

if ($isValid) {
    echo 'JWT is valid.';
} else {
    echo 'JWT is invalid.';
}

Full Example Code

Here's the complete code snippet for reference:

use GuzzleHttp\Client;
use Jose\Component\Checker\{AlgorithmChecker,
    AudienceChecker,
    ClaimCheckerManager,
    ExpirationTimeChecker,
    HeaderCheckerManager,
    IssuedAtChecker,
    IssuerChecker,
    NotBeforeChecker
};
use Jose\Component\Core\{AlgorithmManager, JWKSet};
use Jose\Component\Signature\{JWSTokenSupport, JWSVerifier};
use Jose\Component\Signature\Algorithm\RS256;
use Jose\Component\Signature\Serializer\CompactSerializer;
        
// Get Customer ID and Project ID from Admin Panel
$customerId = 'CUSTOMER-ID';
$projectId = 'PROJECT-ID';
$audience = 'YOUR-RP-ID';

// Get JWT Token after successful login
$jwtToken = 'USER-JWT-TOKEN';

// Deserialize the JWT to get the header and payload
$serializer = new CompactSerializer();
$jws = $serializer->unserialize($jwtToken);

// Header checks
$headerCheckerManager = new HeaderCheckerManager(
    checkers: [new AlgorithmChecker(['RS256'])],
    tokenTypes: [new JWSTokenSupport()],
);
$headerCheckerManager->check(
    jwt: $jws,
    index: 0,
    mandatoryHeaderParameters: ['alg', 'kid'],
);

// Claim checks
$clock = new Clock();

$claimCheckerManager = new ClaimCheckerManager(checkers: [
    new IssuedAtChecker(allowedTimeDrift: 0, protectedHeaderOnly: true, clock: $clock),
    new NotBeforeChecker(allowedTimeDrift: 0, protectedHeaderOnly: true, clock: $clock),
    new ExpirationTimeChecker(allowedTimeDrift: 0, protectedHeaderOnly: true, clock: $clock),
    new AudienceChecker(audience: $audience, protectedHeader: true),
    new IssuerChecker(issuers: ['api.authopia.io'], protectedHeader: true),
]);

$claimCheckerManager->check(
    claims: $claims = json_decode($jws->getPayload(), true),
    mandatoryClaims: ['iat', 'nbf', 'exp', 'email', 'projectId'],
);

// Verify project identifier
if ($claims['projectId'] !== $projectId) {
    throw new \RuntimeException('Project ID does not match.');
}

// Fetch the JWKS
$client = new Client();
$response = $client->get(sprintf('https://api.authopia.io/v1/customers/%s/.well-known/jwks.json', $customerId));
$jwks = json_decode($response->getBody()->getContents(), true);
$jwkSet = JWKSet::createFromKeyData($jwks);

// Get key id (kid) from headers
$kid = $jws->getSignature(0)->getProtectedHeader()['kid'];

// Match the correct key using 'kid'
$key = $jwkSet->get($kid);

// Configure JWT verification components
$algorithmManager = new AlgorithmManager([new RS256()]);
$jwsVerifier = new JWSVerifier($algorithmManager);

// Verify the JWT's signature
$isValid = $jwsVerifier->verifyWithKey($jws, $key, 0);

if ($isValid) {
    $userEmail = $claims['email'];
    // JWT is verified and valid
    echo 'JWT is valid. User email: ' . $userEmail;
} else {
    // JWT is invalid
    echo 'JWT is invalid.';
}

Follow these steps to securely validate JWTs in your application. Should you encounter any issues or have further questions, feel free to reach out to our support team.

Last updated