Xoriant Blog on Microservices Security Using JWT Authentication Gateway (2024)

Introduction

This blog provides a deep dive on the use of an Authentication Gateway for providing secured access to Microservices. It describes how the Gateway uses JSON Web Token(JWT) for authenticating clients that want to access web service endpoints hosted by different Microservices.

JSON Web Token (JWT)

As per RFC 7519, JWT is a compact and self-contained way for secure transmission of information between different entities as a JSON object. The token is composed of 3 parts: header, payload and signature; each separated by a dot as mentioned in below format:

header.payload.signature

Header

The header typically consists of two parts:

  1. Type of Token i.e. JWT
  2. Hashing algorithm being used e.g. HMAC SHA256 or RSA

e.g.

{ "alg": "HS256", "typ": "JWT"}

The first part of the JWT is formed by Base64Url encoding of the Header JSON.

Payload

The payload contains the ‘claims’ i.e. the data that‘s stored inside the JWT. Claims are information about an entity (typically, the user) and additional data.

e.g.

{ "sub": "1234567890", "name": "Mark", "admin": true, "iss": http://abc.com, "iat": 1472033308, "exp": 1472034208}

The second part of the JWT is formed by Base64Url encoding of the payload.

Signature

The signature is created by signing the encoded header & encoded payload with a secret key using the algorithm specified in the header.

e.g. For HMAC SHA256 algorithm, the signature will be created as below:

HMACSHA256( base64UrlEncode(header) + "." +base64UrlEncode(payload), secret)

After combining all JWT components together, a typical JWT token looks like below.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM

The JWT token should be sent in the Authorization header using the Bearer schema for accessing a protected resource as shown below:

Authorization: Bearer <JWT token>

JWT Advantages

  1. JWT uses JSON which is less verbose than XML & therefore smaller in size making it more compact than Security Assertion Markup Language Tokens (SAML)
  2. It is easier to work with JWT than SAML assertions as JSON parsers are common in most programming languages but XML doesn't have a natural document-to-object mapping.

System Architecture

The system is implemented as a bunch of Spring Boot applications communicating with each other. Apache Maven is used as a dependency & build tool for the applications. The system components are best understood from the below workflow diagram.

System Components & description:

  1. Client

It can be any Web service consumer on any platform. Simply put it can be another Webservice, UI application or Mobile platform which wants to read-write data in a secure way with an Application having various Microservices. Typically, the Client requests shown in the above workflow diagram can be REST API requests invoked from any REST client.

  1. Eureka Service Registry

The Eureka service registry is another Microservice that primarily handles the communication of different Microservices. As each Microservice registers itself in the Eureka server, it must be highly available since every service communicates with it to discover other services. Below is the configuration for a typical Eureka server.

pom.xml

<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency></dependencies>

application.yml

server: port: ${PORT:8761}eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
@SpringBootApplication@EnableEurekaServerpublic class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); }}

Navigating the browser tohttp://localhost:8761shows theEurekadashboard, where one can inspect the registered Microservice instances & other status information.

3.MicroServices A & B

These are the Microservices that host various REST endpoints namely GET/POST/PUT/DELETE for different resources of the application. Each Microservice is a Eureka client application & must register itself with the Eureka Server. Below is the configuration for MicroserviceA that is hosted on port 8082 with some REST endpoints.

pom.xml

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency></dependencies>

application.yml

server: port: ${PORT:8082}spring:  application: name: greeting-serviceeureka: client: fetchRegistry: true serviceUrl: defaultZone: http://localhost:8761/eureka/
@SpringBootApplication@EnableDiscoveryClient@RestController@RequestMapping("/greetings")public class MicroServiceA { @GetMapping public String fetchGreeting() { return "Hello from MicroServiceA"; } @PostMapping public String addGreeting(@RequestBody String greeting) { // Business logic to save the greeting typically to a DB table return "Greeting successfully saved"; } public static void main(String[] args) { SpringApplication.run(MicroServiceA.class, args); }}

On similar lines, MicroserviceB can be configured on another port to host other REST endpoints.

4.Authentication Gateway

The Gateway is implemented as a Microservice using Spring Cloud Zuul Proxy & Spring Security APIs. It handles centralized authentication & routing client requests to various Microservices using the Eureka service registry. It acts as a proxy to the clients abstracting the Microservices architecture & must be highly available as it works as a single point of interaction for different operations be it user signup, user credentials authentication, generating & verifying JWT tokens & handling the client business requests by communicating to relevant Microservice endpoints. In short, it is a Request router that doubles up as an Authentication Microservice.

Alternatively, the User management features including different authentication mechanisms (JWT, OAuth) can also be hosted as a separate ‘User Management’ Microservice. In that case, the Gateway can just work as a lightweight request router & communicate with it for user authentication via the JWT tokens.

Below is the configuration for the Authentication Gateway.

pom.xml

 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>  <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency> <!—- Other dependencies -- > 

application.yml

server: port: ${PORT:8081}eureka: client: fetchRegistry: true serviceUrl: defaultZone: localhost:8761zuul: routes: serviceA: path: /greetings-api/** serviceId: greeting-service serviceB: path: /tasks-api/** serviceId: task-service

application.properties

jwt.security.key=j3H5Ld5nYmGWyULy6xwpOgfSH++NgKXnJMq20vpfd+8=t

The JWT secret key is used during the signing of the JWT token

@SpringBootApplication@EnableDiscoveryClient@EnableZuulProxypublic class ApiGatewayApplication{ public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // For encrypting user password }}

JWT Authentication Workflow

  1. Client registers with Authentication Gateway by supplying the username & password through the POST URI /users/signup (which is permitted for public access without any security)

Web security configuration

@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource(name = "userService") private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; @Autowired private BCryptPasswordEncoder passwordEncoder; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception{  return super.authenticationManagerBean(); } @Autowired public void globalUserDetails(AuthenticationManagerBuilder auth) throws  Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder); } @Bean public JwtAuthenticationFilter authenticationTokenFilterBean() throws  Exception { return new JwtAuthenticationFilter(); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable()  .authorizeRequests() .antMatchers("/token/*","/users/signup").permitAll() .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler) .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); }}

UserController

@RestController@RequestMapping("/users")public class UserController { @Autowired private UserService userService; @PostMapping("/signup") public User saveUser(@RequestBody LoginUser user){ return userService.save(user); } // Other methods}

Here UserService is an implementation of Spring Security’s UserDetailsService & the password is encrypted using BCryptPasswordEncoder before storing itinto the DB.

@Service(value = "userService")public class UserServiceImpl implements UserDetailsService, UserService { @Autowired private UserRepository userDao;  @Autowired private BCryptPasswordEncoder passwordEncoder;  @Override public User save(LoginUser user) { User newUser = new User(); newUser.setUsername(user.getUsername()); newUser.setPassword(passwordEncoder.encode(user.getPassword())); return userDao.save(newUser); } public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { User user = userDao.findByUsername(userId); if(user == null){ throw new UsernameNotFoundException("Invalid username or password."); } return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), getAuthority()); } // Other service methods

2.Client requests an ‘Access token’ from Authentication Gateway through the POST URI /token/generate-token by sending their credentials.

3.The Authentication Gateway verifies the credentials & upon successful authentication generates a JWT access token containing user details and permissions. This token is sent as a response to the client.

AuthenticationController

@RestController@RequestMapping("/token")public class AuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserService userService; @RequestMapping(value = "/generate-token", method = RequestMethod.POST) public ResponseEntity<?> generateToken(@RequestBody LoginUser loginUser)  throws AuthenticationException { final Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginUser.getUsername(), loginUser.getPassword() ) ); SecurityContextHolder.getContext().setAuthentication(authentication); final User user = userService.findOne(loginUser.getUsername()); final String token = jwtTokenUtil.generateToken(user); return ResponseEntity.ok(new AuthToken(token)); }}

JwtTokenUtil

@Componentpublic class JwtTokenUtil implements Serializable {  @Value("${jwt.security.key}") private String jwtKey; private String doGenerateToken(String subject) { Claims claims = Jwts.claims().setSubject(subject); return Jwts.builder() .setClaims(claims) .setIssuer("http://jwtdemo.com") .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() +  ACCESS_TOKEN_VALIDITY_SECONDS*1000)) .signWith(SignatureAlgorithm.HS256, jwtKey) .compact(); } // Other methods}

Here client will invoke below POST request to obtain an access token having a validity of configured time (few minutes to few hours):

Localhost:8081

Sample Request Body:

{ "username": "admin", "password": "password"}

JWT access token returned by Authentication Gateway

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInNjb3BlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0seyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vand0ZGVtby5jb20iLCJpYXQiOjE1MTg3NjM0NTUsImV4cCI6MTUxODc4MTQ1NX0.t8UUBrhYx6lUAunl5R-s17IxZXOZ1yYGLwV0Sdiw4QY

4.Client then sends the Access token in an Authorization header in each REST API request to the Authentication Gateway.

e.g. To access the GET URI ‘greetings’ using below details:

URL: http://localhost:8081/greetings-api/greetings

Authorization Header: Bearer

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInNjb3BlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0seyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vand0ZGVtby5jb20iLCJpYXQiOjE1MTg3NjM0NTUsImV4cCI6MTUxODc4MTQ1NX0.t8UUBrhYx6lUAunl5R-s17IxZXOZ1yYGLwV0Sdiw4QY

5.Authentication Gateway retrieves the access token from Authorizationheader in the client request and validates the signature. If the signature is valid it routes the request to the matching endpoint (Microservice) based upon the routes which are configured in application.yml or application.properties. Microservice level authorization can also be handled through the JwtAuthenticationFilter.

e.g. All endpoints of MicroserviceA can be accessed by users having ADMIN role only

JwtAuthenticationFilter

@public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse  res, FilterChain chain) throws IOException, ServletException { String header = req.getHeader(HEADER_STRING); String username = null; String authToken = null; if (header != null && header.startsWith(TOKEN_PREFIX)) { authToken = header.replace(TOKEN_PREFIX,""); try { username = jwtTokenUtil.getUsernameFromToken(authToken); } catch (IllegalArgumentException e) { logger.error("An error occured during getting username from  token", e); } catch (ExpiredJwtException e) { logger.warn("Token is expired and not valid anymore", e); } catch(SignatureException e){ logger.error("Authentication Failed. Username or Password not  valid."); } } else { logger.warn("Couldn't find bearer string, will ignore the header"); } if (username != null &&  SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails =  userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new  UsernamePasswordAuthenticationToken(userDetails, null, null); authentication.setDetails(new  WebAuthenticationDetailsSource().buildDetails(req)); logger.info("Authenticated user " + username + ", setting security  context");  SecurityContextHolder.getContext().setAuthentication( authentication); } } chain.doFilter(req, res); }}

6.The gateway can also send extra parameters in the request header (JWT token, other user information etc.) through custom Zuul Filters.

PreFilter

public class PreFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(PreFilter.class); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); // Add a custom header in the request ctx.addZuulRequestHeader("Authorization", request.getHeader("Authorization")); log.info(String.format("%s request to %s", request.getMethod(),  request.getRequestURL().toString())); return null; }}

7.The Micro service then can optionally authorize the request & provides the response to the Authentication Gateway.

For Authorization, the Microservice would need the JWT access token to be passed to it. It can then verify the JWT token & extract the user roles from the claims & accordingly allow/deny the request for the concerned endpoint.

e.g. For authorizing only users with ADMIN role to access the REST endpoint for ‘addGreeting’ in MicroServiceA, it can be annotated as below.

 @PostMapping @PreAuthorize("hasRole('ROLE_ADMIN')") public String addGreeting(@RequestBody String greeting) { // Business logic to save the greeting typically to a DB table return "Greeting successfully saved"; }

8.The Gateway then can optionally add any other header to the response using a ‘post’ Zuul filter & send it back to the client.

Conclusion

JWT Authentication Gateway provides very a useful approach for securing Microservices applications with minimal impact to the Microservices code. Thus, application developers can focus on the core business logic without worrying about the security mechanism that guards the application. It can be independently scaled and deployed for performing load testing & maintaining high availability. It can also serve as a centralized entity for other cross-cutting concerns like Microservices monitoring, routing rules etc.

Further Reading and Next Steps:

  1. Provide a mechanism of fault tolerance for Microservices using Hystrix
  2. Prevent the ‘User Signup’ URL from abuse i.e. prevent attackers from misusing it or provide a limit on the number of signups per minute/hour etc.
  3. Provide endpoints for ‘Password Reset’ & ‘Forgot Password’ to the client
  4. JWT Refresh tokens
  5. Provide API documentation for each Microservice using Swagger

References:

  1. Jwt.io/introduction
  2. Dzone.com

Found this Xoriant blog interesting? To get more insights on the recent technology trends and thought leadership,VisitOur New BlogsorXoriant Home Page

Xoriant Blog on Microservices Security Using JWT Authentication Gateway (2024)

FAQs

How to secure microservices using JWT? ›

Every Microservice can generate its own JWT token or request it to the Identity Provider that will be passed on every request to the other Microservices, which can verify the authenticity of the token and allow or deny the request.

What is the best way to authenticate between microservices? ›

The best authorization methods for microservices include OAuth 2.0 for delegated access, OpenID Connect for identity layer on top of OAuth, JWT (JSON Web Tokens) for stateless authentication, Role-Based Access Control (RBAC) for managing permissions, and API Gateways to enforce policies and centralize security concerns ...

Is JWT enough for security? ›

It's important to note that a JWT guarantees data ownership but not encryption. The reason is that the JWT can be seen by anyone who intercepts the token because it's serialized, not encrypted. It is strongly advised to use JWTs with HTTPS, a practice that extends to general web security.

How do I secure API gateway in microservices? ›

Token-Based Authentication for Secure Communication

Implementing this approach adds an extra layer of security, ensuring that only those with the right credentials can access your microservices. It's like having a special key to open specific doors within your application.

How to secure a REST API using JWT? ›

Procedure
  1. Make sure that the JWT authentication is enabled for REST APIs by setting the value of servlet. jwt. auth. ...
  2. The incoming HTTP request for REST API call must contain the request header “Authorization” with scheme “Bearer” followed by JWT. The signature of the token and expiration date is verified by the system.

How do I make my JWT token more secure? ›

JWT Security Best Practices
  1. JWTs Used as Access Tokens.
  2. Avoid JWTs With Sensitive Data on the Front Channel.
  3. What Algorithms to use.
  4. When to Validate the Token.
  5. Always Check the Issuer.
  6. Always Check the Audience.
  7. Make Sure Tokens are Used as Intended.
  8. Don't Trust All the Claims.

Which of the following are best practices for microservices security? ›

Here are eight best practices for securing your microservices.
  • Use OAuth for user identity and access control. ...
  • Use 'defence in depth' to prioritize key services. ...
  • Don't write your own crypto code. ...
  • Use automatic security updates. ...
  • Use a distributed firewall with centralized control. ...
  • Get your containers out of the public network.

How did you authenticate security your microservice web services? ›

One common approach to implement authentication in microservices is to use a centralized identity provider (IdP) that issues tokens to authenticated users or services. Tokens are typically JSON Web Tokens (JWTs), which are digitally signed and contain claims about the identity and attributes of the token holder.

What is better than JWT security? ›

Security: OAuth is a secure way to manage authorization flows, while JWT is a lightweight and self-contained token. It does not provide security on its own, but can be secure as part of a well designed authentication system.

What are the downsides of using JWT for authentication? ›

Disadvantages of JWT Authentication:

Token Size: JWTs can become large if they carry extensive user data, leading to increased network traffic. You should strike a balance between token size and necessary information. Limited Token Expiry Control: Once issued, JWTs remain valid until they expire.

What is more secure than JWT? ›

Secure: Opaque tokens do not contain any user information, making them more secure than JWT tokens. Flexible: Opaque tokens can be customized to store additional user information in the authorization server, which can be retrieved by the resource server when needed.

Which API gateway is best for microservices? ›

Using Kong for Your Microservices Gateway

Kong Gateway is a leading open-source API gateway and service mesh made for hybrid and multi-cloud environments. Kong provides essential features for secure, scalable microservices: Service discovery and routing. Load balancing algorithms.

How do you provide security in microservices? ›

Here are seven best practices to protect the integrity of your application:
  1. Use shift-left and DevSecOps strategies. ...
  2. Make applications secure by design. ...
  3. Practice defense-in-depth. ...
  4. User authentication and authorization. ...
  5. Create API gateways. ...
  6. Ensure container security. ...
  7. Secure service-to-service communication.
Nov 17, 2022

How do I make my API gateway secure? ›

You can protect your API using strategies like generating SSL certificates, configuring a web application firewall, setting throttling targets, and only allowing access to your API from a Virtual Private Cloud (VPC).

How to secure Spring Boot microservices with JWT? ›

Spring Boot - How Another Microservice Validate JwT Token when 2 Microservice communicate each other. You can handle it with writing an interceptor in your Microservice B and when you send request from A to B put JWT token in your header and decode your token in B and check that token is valid or not .

How do you make microservices secure? ›

8 Ways to Secure Your Microservices Architecture
  1. Make your microservices architecture secure by design. ...
  2. Scan for dependencies. ...
  3. Use HTTPS everywhere. ...
  4. Use access and identity tokens. ...
  5. Encrypt and protect secrets. ...
  6. Slow down attackers. ...
  7. Know your cloud and cluster security. ...
  8. Cover your security bases.

Why use JWT in microservices? ›

JSON Web Token (JWT) provides a mechanism for sharing a set of claims or properties from a client to a microservices application in an encrypted and secure way. JWTs can also secure communication between services or pass end-user context and data between microservices.

How do you securely communicate between microservices? ›

Another way to ensure secure communication between microservices is to implement an API gateway. An API gateway is a single entry point for all the client requests to your microservices. It can provide various security functions, such as authentication, authorization, rate limiting, throttling, logging, and encryption.

Top Articles
Latest Posts
Article information

Author: Manual Maggio

Last Updated:

Views: 5979

Rating: 4.9 / 5 (69 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Manual Maggio

Birthday: 1998-01-20

Address: 359 Kelvin Stream, Lake Eldonview, MT 33517-1242

Phone: +577037762465

Job: Product Hospitality Supervisor

Hobby: Gardening, Web surfing, Video gaming, Amateur radio, Flag Football, Reading, Table tennis

Introduction: My name is Manual Maggio, I am a thankful, tender, adventurous, delightful, fantastic, proud, graceful person who loves writing and wants to share my knowledge and understanding with you.