#1: Understanding JWTs: The Complete Guide to API Authentication
APIs are the backbone of modern applications, allowing for a clean separation between business logic and clients / users. As applications move into production, secure authentication mechanisms are essential. JSON Web Tokens (JWTs) have emerged as one of the most widely adopted solutions for API authentication and authorization.
This post kicks off our series on "Mastering API Security with JWTs". What they are, how they work, and why they are critical to authentication systems.
What is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe token used to represent claims securely between two parties. At its core, a JWT is a digitally signed piece of data that allows a server to verify a user's identity and permissions without needing to query a database or maintain session state.
JWTs are an open standard (RFC 7519) and are commonly used in stateless authentication systems.
Why Use JWTs?
JWTs are ideal for:
- Single Sign-On (SSO): Authenticate once, access multiple systems
- Microservice Architectures: Share authentication information across services
- Stateless Authentication: No need to store sessions on the server, the need to query the database for certain operations may be reduced
Getting and Using a JWT
When a user logs in with valid credentials (such as a username and password), the server responds with a JSON Web Token (JWT). This token represents the user’s authenticated session and can be used to access protected parts of your application or API.
Since the JWT essentially acts as a key to your system, it’s important to handle it securely — store it carefully, avoid keeping it longer than necessary, and never include sensitive information inside it unless it's encrypted.
The following diagram shows how a JWT token is obtained and utilized:
So what's going on here?
- Authorization Request: The client application initiates an authorization request to the authorization server using a specific flow (such as username / password auth).
- Token Issuance: If the request is approved the authorization server responds with a JWT token
- Accessing Protected Resources: The application can then use the JWT token to make requests to protected resources.
The terms "Authorization Server" and "Resource Server" used to confuse me when I was first picking up on this stuff.
- Authorization Server: Any server that accepts credentials (username / password) and returns tokens. Its a really generic and flexible term. Its the server that you "login" to.
- Resource Server: Any API that uses your JWT to authenticate against. Again, its very generic and flexible. Typically, these are our own internal workloads / APIs.
Many options and providers are available for authorization servers such as: Auth0, Cognito, etc. You can choose to use a pre-built solution, or build out your own.
To access a protected route, the client (usually a browser or mobile app) sends the JWT along with the request. This is done by including an HTTP Authorization header using the Bearer schema. It looks like this:
Authorization: Bearer <your_jwt_token>
The server then checks the Authorization
header on each request. If the token is valid and hasn't expired, the user is granted access to the resource. If the JWT includes all the necessary information, it can reduce or even eliminate the need to query the database for certain operations — though this won't always apply in every scenario.
Keep in mind: although JWTs are signed and can't be tampered with, they’re still readable by anyone. Always avoid putting private or sensitive data (like passwords or secrets) in the token payload.
JWT Structure
A JWT has three parts, seperated by dots (.
):
<Header>.<Payload>.<Signature>
-
Header: Contains metadata about the token. Typically two parts: the type of token which is JWT. And the signing algorithm being used, such as HMAC SHA256.
For example:
{ "alg": "HS256", "typ": "JWT" }
This JSON is Base64 encoded to form the header portion of the JWT.
-
Payload: This section contains the core data of the token — known as claims. Claims are pieces of information about the subject (often the user) and other relevant details. Claims come in three main categories: standard (registered), public, and private. In this series, We'll focus on standard and private claims.
- Standard (registered) claims: These are predefined, commonly used claims that are optional but recommended. They help ensure consistency and interoperability across systems. Examples include:
iss
(issuer),exp
(expiration time),sub
(subject),aud
(audience), among others. Personal note: I strongly recommend using all of the standard claims. More on that in Part 2! - Private claims: These are custom claims defined by the application developer. They are quite powerful and can assert ANY data about our users. Some examples may include: a users email address, assigned role, or whether the user is an admin.
For example:
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "role": "Admin" }
This JSON is Base64 encoded to form the payload portion of the JWT.
- Standard (registered) claims: These are predefined, commonly used claims that are optional but recommended. They help ensure consistency and interoperability across systems. Examples include:
-
Signature: Verifies that the sender says who they says they are and that the token contents have not been changed
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
A Real-World Example
Here is an example of a JWT base on our example above:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImVlNDJkMTQyOGU4MDI2MzI1ODRlZDQ1NDlhZTRlNmI4In0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlIjoiQWRtaW4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
At first glance, it might look like gibberish — but take a closer look. Each section is Base64 encoded and separated by a dot (.
). These three segments represent the header, payload, and signature.
To decode it and see the actual contents, use an online JWT decoder (such as jwt.io).
Common JWT Misconceptions
- "JWTs are encrypted": No, they are signed, not encrypted. Anyone can read the payload unless encryption is explicitly applied.
- "JWTs are inherently secure": Only if used correctly. Never store sensitive information in the payload. Ensure mitigations against common attacks are taken.
- "JWTs never expire": Good practice dictates settings short expiration times with refresh token support
- "Stateless always means better": Stateless tokens have trade-offs, especially in scenarios where immediate revocation is required.
Setting the Foundation
This post provides a foundation for understanding JWTs, setting the stage for deeper exploration and use cases in future posts. JWTs are powerful but nuanced tools. With the right implementation, they offer scalable, stateless, and secure authentication for modern APIs.