visit
{
"iss": "//accounts.google.com",
"azp": "00.apps.googleusercontent.com",
"aud": "00.apps.googleusercontent.com",
"sub": "0062367",
"at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
"email": "[email protected]",
"email_verified": "true",
"iat": 1353601026,
"exp": 1353604926,
"nonce": "0-2490358",
"hd": "example.com"
}
If the auth server sends it as a plain JSON, the client application’s APIs would have no way to verify that the content they are receiving is correct. A malicious attacker could, for example, change the user ID (sub
claim in the above example JSON), and the application’s APIs would have no way to know that that has happened.
You may have noticed that in the JWT (that is issued by Google) example above, the JSON payload has non-obvious field names. They use sub
, iat
, aud
and so on:
iss: The issuer of the token (in this case Google)
azp and aud: Client IDs issued by Google for your application. This way, Google knows which website is trying to use its sign in service, and the website knows that the JWT was issued specifically for them.
sub: The end user’s Google user ID.
at_hash: The hash of the access token. The OAuth access token is different from the JWT in the sense that it’s an opaque token. The access token’s purpose is so that the client application can query Google to ask for more information about the signed in user.
email: The end user’s email ID
email_verified: Whether or not the user has verified their email.
iat: The time (in milliseconds since epoch) the JWT was created
exp: The time (in milliseconds since epoch) the JWT was created
nonce: Can be used by the client application to prevent replay attacks.
hd: The hosted G Suite domain of the user
The reason for using these special keys is to follow an industry convention for the names of important fields in a JWT. Following this convention enables client libraries in different languages to be able to check the validity of JWTs issued by any auth servers. For example, if the client library needs to check if a JWT is expired or not, it would simply look for the iat
field.
{
"userId": "abcd123",
"expiry": 01
}
NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyHkHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=
HMAC + SHA256
, also known as HS256
.{
"typ": "JWT",
"alg": "HS256"
}
First, we remove all the spaces from the payload JSON and then base64 encode it to give us eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ
. You can try pasting this string in an to retrieve our JSON.
Similarly, we remove the spaces from the header JSON and base64 encode it to give us: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.
We concatenate both the base 64 strings, with a .
in the middle like <header>.<payload>
, giving us eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ
. There is no special reason to do it this way other than to set a convention that the industry can follow.
Now we run the Base64 + HMACSHA256
function on the above concatenated string and the secret to give us the signature:
Base64URLSafe(
HMACSHA256("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ", "NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyHkHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=")
)
Results in:
3Thp81rDFrKXr3WrY1MyMnNK8kKoZBX9lg-JwFznR-M
We base64 encode it only as an industry convention.
Finally, we append the generated secret like <header>.<body>.<secret>
to create our JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ.3Thp81rDFrKXr3WrY1MyMnNK8kKoZBX9lg-JwFznR-M
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
).{"typ":"JWT","alg":"HS256"}
typ
field's value is JWT
and the alg
is HS256
. If not, it would reject the JWT.Base64URLSafe(HMACSHA256(...))
operation as step number (4) on the header and body of the incoming JWT. Note that if the incoming JWT's body is different, this step will generate a different signature than in step (4).eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ
) to give us {"userId":"abcd123","expiry":01}
.expiry
time (since the JWT is expired).
Secure: JWTs are digitally signed using either a secret (HMAC) or a public/private key pair (RSA or ECDSA) which safeguards them from being modified by the client or an attacker.
Stored only on the client: You generate JWTs on the server and send them to the client. The client then submits the JWT with every request. This saves database space.
Efficient / Stateless: It’s quick to verify a JWT since it doesn’t require a database lookup. This is especially useful in large distributed systems.
Non-revocable: Due to their self-contained nature and stateless verification process, it can be difficult to revoke a JWT before it expires naturally. Therefore, actions like banning a user immediately cannot be implemented easily. That being said, there is a way to maintain , and through that, we can revoke them immediately.
Dependent on one secret key: The creation of a JWT depends on one secret key. If that key is compromised, the attacker can fabricate their own JWT which the API layer will accept. This in turn implies that if the secret key is compromised, the attacker can spoof any user’s identity. We can reduce this risk by changing the secret key from time to time.
aud
claim would point to App1’s ID).