b. JWT
The first step in building the security layer is understanding JSON Web Tokens (JWT) and creating the auth interface module that defines the API contract for authentication.
1. JSON Web Token
JWT is a compact, URL-safe way to represent claims between two parties. It is the standard used by the platform to carry the authenticated user's identity across services without storing server-side session state β making it naturally compatible with horizontal scalability.
A JWT consists of three Base64Url-encoded parts separated by dots.
Example JWT
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI1ZjNjOWEyZS04YjFkLTRjN2EtOWU2Zi0yYTRiOGMxZDNlNWYiLCJpc3MiOiJJbnNwZXI
6OlBNQSIsImVtYWlsIjoiYWNtZUBpbnNwZXIuZWR1LmJyIiwic3ViIjoiQW50b25pbyBkbyBFc3R1ZG8iLCJuYmYiOjE3ODc1NzY
0MDAsImV4cCI6MTc4NzU4MDAwMH0.KeKkcoXFaiijMekCSRk8rUrh69RXEzOZmP1U23MIb0E
The header declares the signing algorithm (alg) and may declare the token type (typ). JJWT, the library used by the platform, writes only alg unless told otherwise β the example above is exactly what JwtService produces.
{
"jti": "5f3c9a2e-8b1d-4c7a-9e6f-2a4b8c1d3e5f",
"iss": "Insper::PMA",
"email": "acme@insper.edu.br",
"sub": "Antonio do Estudo",
"nbf": 1787576400,
"exp": 1787580000
}
The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. There are three types of claims: registered, public, and private claims. Registered claims are a set of predefined claims which are not mandatory but recommended, to provide a set of useful, interoperable claims. Some examples are iss (issuer), exp (expiration time), sub (subject), and aud (audience). Public claims can be defined at will by those using JWTs. To avoid collisions they should be defined in the IANA JSON Web Token Registry or be defined as a URI that contains a collision resistant namespace. Private claims are the custom claims created to share information between parties that agree on using them and are neither registered nor public claims.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
Base64.decode(JWT_SECRET_KEY) // 32 bytes = 256 bits
)
To create the signature part, you take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.
HS256 is HMAC with SHA-256 and needs a key of at least 256 bits. In the platform, JwtService.getKey() returns Keys.hmacShaKeyFor(Base64.getDecoder().decode(secretKey)): the key is the Base64-decoded store.jwt.secretKey (environment variable JWT_SECRET_KEY), 32 bytes β generate one with openssl rand -base64 32. signWith(key) lets JJWT pick the algorithm from the key size: 256 bits β HS256 (a 384-bit key would give HS384, a 512-bit key HS512; a key under 256 bits is rejected with a WeakKeyException). The example token above was signed this way with the development default of application.yaml β paste it into jwt.io with that secret (marked as Base64) to check the signature.
Secret Key
The secret key used to sign the JWT must be kept confidential. Anyone with access to it can forge tokens. The application.yaml of auth-service reads it from the environment variable JWT_SECRET_KEY and ships a development default only so the stack runs out of the box β that default is public (it is in the repository), so outside your machine always set JWT_SECRET_KEY with a key of your own and never hardcode a real one in source code.
The key claims used in this platform are:
| Claim | Field | Description |
|---|---|---|
jti | id | The account's unique identifier |
sub | subject | The account's name |
iss | issuer | The issuing platform |
nbf | notBefore | Token becomes valid at this timestamp |
exp | expiration | Token expires at this timestamp |
email | claims(Map.of("email", β¦)) | A private claim: the account's email |
After a successful login, the server generates a JWT, sets it in an HttpOnly cookie, and returns it to the browser. For every subsequent request, the browser sends the cookie automatically β the Gateway reads it and resolves the identity without asking the user to log in again.
sequenceDiagram
autonumber
actor User
User->>+Auth Service: login (email + password)
Auth Service->>Auth Service: validates credentials and generates JWT
Auth Service->>-User: Set-Cookie: __store_jwt_token=<jwt>
User->>User: browser stores the cookie
User->>Gateway: subsequent requests carry the cookie automatically 2. The auth Interface Module
The auth module follows the same pattern as the account module: it is a library (not a runnable application) that defines the API contract and DTOs shared between the auth-service and any other microservice that needs to call the auth endpoints via Feign.
2.1 Repository
Create a new git repository for the auth interface and add it as a submodule:
2.2 Code
Use Spring Initializr to generate a Maven project with the following settings:
- Group:
store - Artifact:
auth - Package name:
store.auth - Packaging: Jar
- Java: 25
Dependencies to add:
- Lombok β reduces boilerplate with compile-time annotation processing
- OpenFeign β declarative HTTP client for inter-service calls
Delete AuthApplication.java, src/test/, and src/main/resources/ β this module is a library, not a runnable service.
The resulting structure is:
π api/
βββ π auth/
βββ π src/
β βββ π main/
β βββ π java/
β βββ π store/
β βββ π auth/
β βββ AuthController.java
β βββ LoginIn.java
β βββ RegisterIn.java
β βββ TokenOut.java
βββ pom.xml
Source
The /auth/solve endpoint
solveToken is an internal endpoint meant for the Gateway: it receives a raw JWT string and returns the corresponding idAccount. It is not an open route, so the browser cannot use it without a valid cookie. me (GET /auth/me) returns the logged-in account: the Gateway fills the id-account header from the token.
2.3 Install
Install the interface to the local Maven repository so auth-service can depend on it:
Done! The auth interface is ready. In the next section we will implement auth-service, including the JwtService that signs and validates tokens, and AuthService that orchestrates login and registration.