diff --git a/chapter10/index.adoc b/chapter10/index.adoc new file mode 100644 index 00000000..ae8ef1ed --- /dev/null +++ b/chapter10/index.adoc @@ -0,0 +1,487 @@ +== Chapter 10: JWT Authentication + +In modern microservices architectures, where services are distributed and stateless, +securing communications between clients and services and between individual services is critical. *JSON Web Token (JWT)* provides a lightweight, self-contained, and efficient user authentication and authorization mechanism, enabling scalable and secure identity propagation across distributed systems. + +*MicroProfile JWT* is a specification that standardizes JWT-based authentication and authorization for Java microservices. Leveraging the JWT open standard https://datatracker.ietf.org/doc/html/rfc7519[RFC 7519] enables services to securely extract and validate claims such as identity, roles, and permissions. + +MicroProfile JWT allows developers to build secure, interoperable, and portable microservices. It supports *role-based access control (RBAC)*, simplifies identity management in stateless services, and avoids vendor lock-in by adhering to open specifications. + +=== Topics to be covered: +- Introduction to JWT Authentication +- Structure of JWT +- Benefits of JWT in Microservices +- Setting up MicroProfile JWT +- Configuring MicroProfile JWT +- Use cases for JSON Web Tokens +- Request Flow in MicroProfile JWT +- Role-Based Access Control (RBAC) +- Setting Token Expiry Times for Security +- Error Handling +- Best Practices for JWT Authentication +- Security Best Practices for Microservices +- Conclusion + +=== Introduction to JWT Authentication + +This section will explore JSON Web Tokens, how they work, and why they are foundational to implementing stateless authentication and authorization in microservices-based systems. + +==== What is a JSON Web Token (JWT)? + +A **JSON Web Token (JWT)** (see https://jwt.io/[JWT.io]), as defined in https://datatracker.ietf.org/doc/html/rfc7519[RFC 7519], is an open standard for securely transmitting information (claims) between parties as a JSON object. JWTs are digitally signed, ensuring their integrity and authenticity. + +=== Structure of a JWT + +A JWT consists of three Base64 encoded parts, separated by dots (+.+): + +[source] +---- +
.. +---- + +- *Header* - It contains metadata about the token, such as token type (type: “JWT”) and signing algorithm ( alg: “RS256” for RSA-SHA256). + +[source, json] +---- +{ + "alg": "RS256", + "typ": "JWT" +} +---- +- *Payload* - It contains claims which are key-value pairs representing data about the user, such as roles and expiration. +Example of claims in a JWT payload: + +[source, json] +---- +{ + "iss": "https://io.microprofile.com/issuer", + "sub": "user1", + "exp": 1735689600, + "iat": 1735686000, + "aud": "my-audience", + "groups": ["user", "admin"] +} +---- + +- *Signature* — A digital signature that verifies the token’s integrity by combining the encoded header, payload, and private key. + +Example JWT Token: +[source] +---- +eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0. +QR1Owv2ug2WyPBnbQrRARTeEk9kDO2w8qDcjiHnSJflSdv1iNqhWXaKH4MqAkQtM +oNfABIPJaZm0HaA415sv3aeuBWnD8J-Ui7Ah6cWafs3ZwwFKDFUUsWHSK-IPKxLG +TkND09XyjORj_CHAgOPJ-Sd8ONQRnJvWn_hXV1BNMHzUjPyYwEsRhDhzjAD26ima +sOTsgruobpYGoQcXUwFDn7moXPRfDE8-NoQX7N7ZYMmpUDkR-Cx9obNGwJQ3nM52 +YCitxoQVPzjbl7WBuB7AohdBoZOdZ24WlN1lVIeh8v1K4krB8xgKvRU8kgFrEn_a +1rZgN5TiysnmzTROF869lQ. +AxY8DCtDaGlsbGljb3RoZQ. +MKOle7UQrG6nSxTLX6Mqwt0orbHvAKeWnDYvpIAeZ72deHxz3roJDXQyhxx0wKaM +HDjUEOKIwrtkHthpqEanSBNYHZgmNOV7sln1Eu9g3J8. +fiK51VwhsxJ-siBMR-YFiA +---- + +==== Types of Claims in MicroProfile JWT + +Claims in JWTs can be categorized into two types: + +===== Standard Claims + +These are predefined claims with specific meanings, as defined by the JWT specification. Some commonly used standard claims include: + +|=== +|*Claim*|*Description*|*Example* + +|`iss`|Issuer, the entity that issued the JWT (e.g., an authentication server)|`"iss": "https://io.microprofile.com/issuer"` +|`sub`|Subject, the principal (user or service) that the JWT is about.|`"sub": "user1"` +|`aud`|Audience, the intended recipients of the token (e.g., specific microservices).|`"aud": "order-service"` +|`exp`|Expiration time|`"exp": 1735689600` +|`nbf`|not before|`"nbf": 1735686000` +|`iat`|issued at, when that token was issued.|`"iat": 1735686000` +|`jti`|Unique JWT token identifier|`"jti": "a1b2c3d4"` +|`groups`|Groups, list of roles or users allowed to access the resource |`["user", "admin"]` +|=== + +===== Custom Claims + +These are application-specific claims that provide additional information about the user or entity. They can extend authorization logic with application-specific claims (e.g., `department`, `region`). Custom claims are not part of the JWT specification but often include domain-specific data, such as user preferences, tenant IDs, or other metadata. MicroProfile JWT allows developers to access these claims programmatically. + +=== Use cases for JSON Web Tokens + +JWTs are versatile tokens commonly used in modern applications for authentication, where they verify the identity of a user or service; for authorization, where they grant access to resources based on roles or permissions; and for information exchange, where they securely transmit data between parties. + +Below are key scenarios where JWTs shine in microservices environments: + +==== Authentication + +JWTs enable stateless authentication in distributed systems. When a user logs in, an authentication service issues a JWT containing claims like sub (user ID) and exp (expiration time). The client sends this token in the `Authorization: Bearer` header of subsequent requests, allowing microservices to verify the user’s identity without requiring repeated authentication. + +For example, a user authenticates with an Auth Service and receives a JWT. This JWT token grants access to other services, such as a product catalog or order management system, without re-authentication. + +==== Authorization (Role-Based Access Control) + +JWTs are also used for authorization, enabling fine-grained access control based on user roles or permissions. The JWT payload typically includes a group or role claim specifying the user’s roles or permissions. For example, a user with the admin role might be allowed to access all resources while a user with the user role might only have access to specific resources. + +MicroProfile JWT integrates seamlessly with Jakarta EE’s `@RolesAllowed` annotation, making it easy to enforce role-based access control (RBAC) in microservices. Role mapping can be configured in _microprofile-config.properties_: + +[source] +---- +mp.jwt.verify.roles=groups +---- + +==== Claims-based identity + +JWTs are often used to represent claims-based identity, where the JWT contains claims representing the user’s identity, such as their name, email address, or other attributes. Applications can use these claims to identify the user and personalize their experience. + +For example, an application might use the email claim to look up the user’s profile information in a database or +display the user’s name on a welcome page using the name claim. + +==== Information Exchange + +JWTs can securely exchange information between parties. The token payload can include custom claims representing the data being exchanged, such as an order ID or user ID. This makes JWTs useful in scenarios like Single Sign-On (SSO) systems, where information needs to be shared across multiple services. + +For example, a JWT might contain an `order_id` claim and a `user_id` claim, which an order management service can use to retrieve and display the user’s order details. + +==== Federation & Single Sign-On (SSO) + +JWTs facilitate identity federation by allowing integration of multiple trusted identity providers (e.g., Active Directory, LDAP) to provide a single sign-on (SSO) experience. In this case, the JWT contains claims representing the user’s identity, which applications can use to identify the user and retrieve their profile information. + +For example, an enterprise SSO system can issue a JWT that grants access to HR, Payroll, and CRM microservices. MicroProfile JWT validates the token’s iss (issuer) and aud (audience) to enforce trust boundaries. + +=== Benefits of using JWT in Microservices + +JWTs are widely used in microservices for the following reasons: + +==== Statelessness & Scalability + +JWTs eliminate the need for centralized session storage. Each token is self-contained, embedding all necessary user claims (e.g., roles, permissions) in its payload. + +Independent Validation: Microservices validate JWTs locally using public keys, avoiding calls to a central authority. This reduces latency and scales horizontally. + +Example: +A payment service validates a JWT’s signature without querying an authentication server. + + +==== Interoperability + +Open Standards: JWTs adhere to RFC 7519, ensuring compatibility across platforms (Java, .NET, Node.js) and frameworks (Spring Boot, Quarkus). + +MicroProfile Integration: MicroProfile JWT standardizes validation and claim extraction, enabling seamless interoperability across Java microservices. + +==== Fine-Grained Authorization + +Role-Based Access Control (RBAC): Map JWT claims (e.g., groups) to Jakarta EE roles using @RolesAllowed. + +==== Decentralized Security + +Propagation Across Services: A JWT issued by an authentication service is propagated across microservices (e.g., the Order Service and the Inventory Service). Each service independently verifies the token and enforces access control. + +Reduced Central Dependency: No need for a central authorization server, simplifying architecture and improving +fault tolerance. + +Example: + +- Authentication Service: Issues a JWT with `sub: "user1"` and `groups: ["user"]`. +- Order Service: Validates the JWT and processes requests if groups include `users`. +- Inventory Service: Revalidates the same JWT without contacting the auth service. + +=== Setting Up MicroProfile JWT + +To use MicroProfile JWT in your project, add the following dependency to your _pom.xml_ (for Maven): + +[source, xml] +---- + + org.eclipse.microprofile.jwt + microprofile-jwt-auth-api + 2.1 + provided + +---- + +For Gradle, add the following to your _build.gradle_: + +[source] +---- +implementation 'org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:2.1' +---- + +=== Configuring MicroProfile JWT Validation + +MicroProfile JWT requires validation rules configuration to be defined in `src/main/resources/microprofile-config.properties` file. Below is an example configuration: + +[source] +---- +# Public key (PEM format) to verify JWT signatures +mp.jwt.verify.publickey.location=META-INF/publicKey.pem + +# Expected issuer (e.g., your OIDC provider) +mp.jwt.verify.issuer=https://auth.example.com + +# Optional: Validate token audience +mp.jwt.verify.audiences=order-service,payment-service +---- + +Explanation: + +- The `mp.jwt.verify.publickey.location` property specifies the location of the public key used to verify the JWT’s signature. + +- The `mp.jwt.verify.issuer` property defines the expected issuer of the JWT, ensuring that tokens are only accepted if issued by a trusted authority. + +- Optionally, the `mp.jwt.verify.audiences` property can specify the allowed audiences for the JWT, ensuring that the token is intended for the service. + +=== Public Key Setup + +Place the PEM-encoded public key in _src/main/resources/META-INF/publicKey.pem_. This key is used to verify incoming JWT signatures. + +== Request Flow in MicroProfile JWT + +Understanding how JWTs are propagated and processed in a microservices architecture is critical to implementing secure and scalable authentication. This section explains the lifecycle of a JWT from client to service, including token extraction, validation, and claim usage. + +=== How JWTs are Propagated in Microservices + +JWTs are propagated via the `Authorization: Bearer` HTTP header across clients and services. + +==== Client-to-Service + +When a client authenticates (e.g., via a login endpoint), it receives a JWT from an authentication service. This token is then included in the header of subsequent requests to microservices. For example, a request header might look like this: + +[source] +---- +GET /api/orders HTTP/1.1 +Authorization: Bearer eyJhbGciOiJSUzI1NiIs… +---- + +==== Service-to-Service + +In microservices architecture, a client sends a JWT token to the initial service (for example, Order Service) using the `Authorization: Bearer` header. The initial service can forward the same token when calling another backend service (for example, the Inventory Service). Each microservice independently validates the JWT to enforce decentralized, stateless security. + +In advanced scenarios involving multiple downstream services, careful consideration must be given to validating the JWT's `aud` (audience) claim to ensure the token is intended for the target service. + +==== Token Extraction + +MicroProfile JWT runtime handles token extraction and validation automatically. The token is parsed and validated as follows: + +- Header Parsing: The runtime extracts the token from the Bearer schema. + +- Decoding: The JWT is split into its header, payload, and signature components. + +==== Token Validation +The token validation involves the following steps: + +- Signature Verification: The public key validates the token’s integrity. + +- Standard Claims Validation: The runtime then validates standard claims: + +. `iss`: It should match the `mp.jwt.verify.issuer` configuration property. + +. `exp` : This checks if the token has not expired. + +. `aud` : Optionally it checks for the included service(s) in `mp.jwt.verify.audiences`. + +If valid, the JWT’s claims populate the `SecurityContext`. Otherwise, MicroProfile JWT rejects the request with a `401 Unauthorized` status. + +=== Accessing JWT claims via `SecurityContext` + +The `SecurityContext` interface (from Jakarta EE) provides programmatic access to JWT claims. Once a token is validated, MicroProfile JWT injects the `JsonWebToken` into the `SecurityContext`, allowing developers to: + +- Retrieve user identity (e.g., `sub` claim). + +- Check user roles (e.g., `groups` claim). + +- Access custom claims (e.g., `tenant_id` claim). + +[source, java] +---- +@GET +@Path("/user-profile") +public String getUserProfile(@Context SecurityContext ctx) { + JsonWebToken jwt = (JsonWebToken) ctx.getUserPrincipal(); + String userId = jwt.getName(); // Extracts the "sub" claim + Set roles = jwt.getGroups(); // Extracts the "groups" claim + String tenant = jwt.getClaim("tenant_id"); // Custom claim + + return "User: " + userId + ", Roles: " + roles + ", Tenant: " + tenant; +} +---- + +The `SecurityContext` simplifies working with JWTs, enabling seamless integration with Jakarta EE’s security annotations like `@RolesAllowed`. By calling `securityContext.getUserPrincipal()`, the application can obtain the `JsonWebToken` instance, which contains all the claims from the JWT. + +== Role-Based Access Control (RBAC) + +MicroProfile JWT simplifies RBAC by mapping JWT claims (e.g., `groups` or `roles`) to Jakarta EE roles. This enables declarative security using the `@RolesAllowed` annotation. This section explains how to configure and use this mapping effectively. + +=== Default Role Mapping with the `groups` Claim + +MicroProfile JWT seamlessly integrates with Jakarta EE’s `@RolesAllowed` annotation to enforce role-based access control in microservices. By default, MicroProfile JWT maps roles from the groups claim in the JWT payload to Jakarta EE roles. The groups claim is a standard JWT claim that represents the roles or groups assigned to the user. For example, a JWT payload might include: + +[source] +---- +{ + "iss": "https://example.com/issuer", + "sub": "user123", + "groups": ["user", "admin"] +} +---- + +In this case, the user has two roles: user and admin. + +=== Securing Endpoints +The roles in the groups claim can be used directly with the `@RolesAllowed` annotation to secure endpoints. + +[source, java] +---- +@Path("/orders") +public class OrderResource { + + @GET + @Path("/{id}") + @RolesAllowed("user") // Only users can access this method + public Response getOrder(@PathParam("id") String id, @Context SecurityContext ctx) { + String user = ctx.getUserPrincipal().getName(); + // Fetch order for the user + return Response.ok("Order for user: " + user + ", ID: " + id).build(); + } + + @DELETE + @Path("/{id}") + @RolesAllowed("admin") // Only admins can access this method + public Response deleteOrder(@PathParam("id") String id, @Context SecurityContext ctx) { + String admin = ctx.getUserPrincipal().getName(); + // Delete order as admin + return Response.ok("Order deleted by admin: " + admin + ", ID: " + id).build(); + } +} +---- + +The `GET /orders/{id}` service is accessible to users, whereas the `DELETE /orders/{id}` is only available to users with the admin role. + +=== Custom Role Mapping + +If your JWT uses a claim other than groups to represent roles (e.g., roles or scopes), you can customize the mapping using the `mp.jwt.verify.roles` property in _microprofile-config.properties_: + +[source] +---- +# Optional: Map roles from the "groups" claim (default behavior) +mp.jwt.verify.roles=groups +---- + +The `groups` claim is the default claim used for role mapping in MicroProfile JWT Authentication. Therefore, you typically do not need to set the `mp.jwt.verify.roles` property unless your JWT uses a different claim name. For example, some identity providers (like OAuth 2.0 servers or OpenID Connect providers) might include roles in claims such as `roles`, `permissions`, or `scope` instead of `groups`. + +In such cases, update the mapping in your _microprofile-config.properties_ file: + +[source] +---- +mp.jwt.verify.roles=roles +---- + +This ensures that MicroProfile JWT Authentication correctly maps the roles for use with Jakarta EE security annotations like `@RolesAllowed`. + +==== How the RBAC Works + +- Token Validation: MicroProfile JWT validates the JWT’s signature and claims. + +- Role Extraction: Roles are extracted from the configured claim (groups by default). + +- Access Control: The `@RolesAllowed` annotation checks if the user’s roles match the required roles. If not, a `403 Forbidden` response is returned. + +This approach ensures fine-grained security while maintaining compatibility with standard JWT practices. + +== Setting Token Expiry Times for Security + +Short token expiry times reduce the surface area for the attackers. Here’s how to configure token expiry effectively: + +=== Configuring Token Expiry + +Set the `exp` claim at issuance: Ensure your authentication service issues tokens with the `exp` claim. + +[source, java] +---- +{ + "exp": 1735689600 // Token expires at 2025-01-01 00:00:00 UTC +} +---- + +MicroProfile JWT automatically validates the `exp` claim during token verification. Beyond standard JWT validation settings, no additional configuration is needed. + +MicroProfile JWT will reject tokens returning a 401 Unauthorized response if: + +- The `exp` claim is missing or invalid. + +- The current time exceeds the `exp` value. + +== Error Handling + +MicroProfile JWT automatically validates tokens and rejects invalid requests with standardized HTTP responses. Common scenarios include: + +=== Invalid Token (e.g., malformed JWT, invalid signature): + +[source] +---- +HTTP/1.1 401 Unauthorized +WWW-Authenticate: Bearer error="invalid_token" +---- + +=== Expired Token (exp claim validation failure): + +[source] +---- +HTTP/1.1 401 Unauthorized +WWW-Authenticate: Bearer error="invalid_token", error_description="Token expired" +---- + +=== Missing Token + +[source] +---- +HTTP/1.1 401 Unauthorized +WWW-Authenticate: Bearer error="missing_token" +---- + +=== Insufficient Permissions (e.g., missing role for @RolesAllowed): + +[source] +---- +HTTP/1.1 403 Forbidden +---- + +=== Best Practices for JWT Authentication + +. Use Standard Claims - Prefer the groups claim for roles unless your identity provider uses a different claim. + +. Consistent Role Names - Ensure role names (e.g., admin, user) are consistent across JWTs and @RolesAllowed annotations. + +. Least Privilege - Assign minimal required roles to endpoints to reduce security risks. + +. Combine with Other Annotations - Use @PermitAll or @DenyAll alongside @RolesAllowed for flexible security policies. + +== Security Best Practices for Microservices + +But with more services comes more complexity, and with more complexity comes a greater risk of security breaches. So, how do you secure your microservices? + +Securing microservices requires a layered approach, combining authentication, authorization, encryption, and monitoring. MicroProfile JWT simplifies access control while adhering to industry standards. Below are best practices tailored for MicroProfile JWT implementations: + +. Enforce Authentication with Validated JWTs: Ensure every request to a microservice includes a valid JWT. Configure MicroProfile JWT to validate tokens using a public key. Reject tokens with invalid signatures, missing claims, or expired exp values. + +. Implement Role-Based Access Control: Restrict endpoint access based on user roles defined in the JWT. Configure role mapping in `microprofile-config.properties` if using non-default claims + +Use Short-Lived Tokens: To minimize exposure to compromised tokens, set short expiration times (exp claim) for JWTs (e.g., 15–30 minutes). + +. Secure Token Transmission: Prevent token interception or tampering by using HTTPS to encrypt data in transit and store tokens in HTTP `Authorization: Bearer` headers (never in URLs or cookies). + +. Manage Cryptographic Keys Securely: Protect keys to sign/verify JWTs by storing public keys in secure locations (e.g., Kubernetes Secrets, AWS KMS). Rotate keys periodically and avoid hardcoding them in source control. + +. Validate and Sanitize JWT Claims: Validate all claims (e.g., iss, aud) in microprofile-config.properties, and Sanitize custom claims before use to prevent injection attacks and misuse of claims. + +. Monitor and Log Security Events: Log JWT validation errors, role mismatches, and token expiration events to detect breaches and audit access patterns. Integrate with monitoring tools (e.g., Prometheus, Grafana) to track anomalies. + +These steps will help you secure your microservices against the most common attacks. + +== Conclusion + +MicroProfile JWT offers a standards-based, interoperable approach for securing microservices. It simplifies identity propagation, access control, and stateless security across distributed services. Integrating with Jakarta EE enables secure, scalable, and interoperable authentication without a session state. + +*Further Reading:* + +* https://datatracker.ietf.org/doc/html/rfc7519[RFC 7519] +* https://github.com/eclipse/microprofile-jwt-auth[MicroProfile JWT 2.1 Spec] +* https://jakarta.ee/specifications/security/3.0/[Jakarta Security 3.0]