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.