JWTs can be either encrypted or signed (or both, sequentially), using either symmetric or asymmetric algorithms.
The process you describe uses a symmetric algorithm, where the same key is used to sign or encrypt the token, and then to verify or decrypt it. This would be appropriate for tokens that are being created and consumed by the same application - for instance, a web app sending a token to the browser, and reading it back from the request.
If one application is creating the token to be used by a different one, it should use an asymmetric algorithm:
- To encrypt something, you take a public key, apply the encryption algorithm, and end up with a string that can only be decrypted with the private key. A common analogy is that the public key acts like a padlock: anyone can snap it shut, but only the holder of the private key can open it afterwards.
- To sign something, you take a private key, apply the signing algorithm, and end up with a message readable by anybody, and verifiable by anyone with the public key. The analogy is that the public key is like a sample of someone's signature: it's not sufficient to forge their signature, but it is sufficient to tell if a signature is real.
In your case, you need the application generating the token to sign it using a private key, which only that application knows. The corresponding public key can then be shared to every user of the service, and stored in the configuration of the application that needs to verify the token.
You don't specify what library you're using, so it's hard to be specific, but what you need to look for is the option to sign your token rather than encrypt it. Then you (or whoever controls the other application) distribute the public key to every endpoint that needs to verify it, and verify that it contains a signature with the expected algorithm and key.
For instance, using the lcobucci/jwt
library, the application creating the token would look like this:
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;
$signer = new Sha256();
$privateKey = new Key('file://{path to your private key}');
$token = (new Builder())->withClaim('some', 'claim')
->getToken($signer, $privateKey);
and the application receiving it would verify it like this:
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Key;
$publicKey = new Key('file://{path to your public key}');
$token = (new Parser())->parse('{token taken from request}');
if ( $token->verify($signer, $publicKey) ) {
var_dump($token->getClaims());
}
else {
echo 'Token does not have valid signature!';
}
As an aside, if you're checking tokens against a known list in a database, you don't really need JWT, you can just use something like a GUID and store the extra data against that in the database. The idea of a JWT is that you don't need any prior knowledge, and rely on checking the signature and the claims inside the token (e.g. using an "expires at" claim to avoid an attacker reusing an old token they found in logs). This trades off the length and complexity of the token for allowing a decentralized and stateless application to read the tokens.