Edge authentication or authorization is a way to validate all the requests before they reach the actual microservice or private networks i.e. at the edge. This is done at a global level for all the services, so that individual microservice do not have to handle the authentication or authorization on thier own.
This removes the security overhead from all microservices so that they can communicate insecurely in the mesh within a private network and focus on only concerned business logic.
A JWT Token is generated at the edge using username & password after a Single Factor Authentication (SFA) in this demo, as in the diagram above. The JWT token is then used in all the subsequent API calls which are validated and authorized at a L7 reverse proxy service like nginx or netflix zuul or spring gateway. Once the token is validated and route access is authorized, the claims from the JWT are decoded and added to the header before passing to the downstream services.
This provides us 5 core benefits -
The internal microservice communication happens based on headers insecurely, cutting down on the latency due to multiple auth calls, and overhead for implementation logic.
All auth logic remains at the edge and reduces the coupling and cyclic dependencies from other microservices.
This is futuristic for upgrades if you want to move from SFA to MFA.
With reverse proxy, all communication between downstream microservices can be based on the headers only and does not need a token or auth mechanism.
No binding of Spring security and roles in code. Instead we utilize Rego Rules from OPA. Hence removing cross-cutting concerns for authorization.
For this session we will focus only on how to build a Edge Auth service in SFA. How to generate a JWT token and the JWKS WebKey Sets using Java. We will utilize auth0 for JWT and nibus-jose libraries for JWKS KeySet. This turns the edge-auth service to behave like a ID server.
In the images added above we have a gateway, which exposes 2 public endpoints -
/login is exposed to get username password and on successful authentication responds with a JWT token and claims like username and user-type.
/.well-known/jwks.json is a public JSON WebSet Keys endpoint which has public key to verify and validate the token which is signed with it’s corresponding private key. This can be used if any external service wants to verify the authenticity of your token. Mostly the JWKS endpoints are public for external services to verify the token whenever required.
Then there is a private endpoint which is token hungry -
/validate - This is to also to Authenticate token but most importantly Authorize and validate whether a requested endpoint or URL with a valid token is allowed access to the user based on it’s user-type. This is controlled using the Open Policy Agent or say OPA.
So a user with user-type admin should be open to all admin API’s but a normal user like a customer should not be allowed to access the admin API. This is controlled using Open Policy Agent and rego rules.
Let’s see how to develop the authentication module first -
The Auth controller for login module and generates JWT token after validating username password. Also, we have the validation endpoints where we generate the JWKS endpoint. And a valiate endpoint for authorization, which we will cover later.
@RestController@RequestMapping("/")@Slf4jpublicclassAuthController{@AutowiredAuthOperationsauthOperations;@PostMapping("/login")publicResponseEntity<String>login(@RequestBodyHashMap<String,String>credentials){log.info("Login Controller..");// call some real validation API with credentials
returnnewResponseEntity<>(authOperations.generateToken(),HttpStatus.OK);}@GetMapping("/.well-known/jwks.json")publicResponseEntity<?>jwksEndpoint(){log.info("JWKS keyset Controller..");HttpHeadersresponseHeaders=newHttpHeaders();responseHeaders.set("Content-Type","application/json");returnnewResponseEntity<>(authOperations.jwksKeySet(),responseHeaders,HttpStatus.OK);}@PostMapping("/validate")publicResponseEntity<Boolean>validate(@RequestBodyHashMap<String,String>requestTokenAndUri){log.info("Authorization token Controller..");returnnewResponseEntity<Boolean>(authOperations.authorize(requestTokenAndUri),HttpStatus.OK);}}
The underlying service operations would be the logic on how to generate a token. We are using the public and private RSA keys for this example. We can store the private key in application.yml also. But for this example we will generate them dynamically.
@Component@Slf4jpublicclassAuthOperations{@AutowiredKeysGeneratorkeys;@Value("${server.port}")privateStringserverPort;UUIDkid=UUID.randomUUID();publicStringgenerateToken(){Stringtoken=null;try{PrivateKeyprivateKey=(PrivateKey)keys.getRSAKeys(KeysGeneratorStrategy.DYNAMIC).get(AuthConstants.PRIVATE_KEY);Datetoday=newDate();Calendarc=Calendar.getInstance();c.setTime(today);c.add(Calendar.DATE,1);Datetomorrow=c.getTime();token=Jwts.builder().setHeaderParam("kid",kid).setId(UUID.randomUUID().toString()).setIssuedAt(today).setSubject("tester").setIssuer("localhost").setExpiration(tomorrow).signWith(privateKey).compact();log.info("Token generated");}catch(Exceptione){log.error("Cannot generate token",e.getMessage());}returntoken;}@CacheablepublicMap<String,List<JSONObject>>jwksKeySet(){try{PublicKeypublicKey=(PublicKey)keys.getRSAKeys(KeysGeneratorStrategy.DYNAMIC).get(AuthConstants.PUBLIC_KEY);JWKjwk=newRSAKey.Builder((RSAPublicKey)publicKey).keyUse(KeyUse.SIGNATURE).keyID(kid.toString()).build();Map<String,List<JSONObject>>keyset=newHashMap<String,List<JSONObject>>();List<JSONObject>keys=newArrayList<JSONObject>();keys.add(jwk.toJSONObject());keyset.put("keys",keys);returnkeyset;}catch(Exceptione){log.error("Cannot generate JWKS keyset",e.getMessage());}returnnull;}publicBooleanauthorize(Stringrequest){// logic for OPA. JWT token and URI validation
}}
Beside the service is the core logic for dynamic RSA keys generation. Here we have 2 strategies to load the private keys, which is defined using the enum. We can read the private key from the file (recommended in case of microservices) and dynamically (just for this example).
Also the JWKS endpoint will return the JSON webset keys with following curl command -
1
curl --location --request GET 'http://localhost:8080/.well-known/jwks.json'
The output would be similar to this -
The authentication source code is available on github where you can explore the The Authorization - Part 2 flow we will be covering in next tutorial along with basics about Open Policy Agent. This whole edge auth module will also be a used in an upcoming tutorial for istio and it’s benefits in kubernetes.
Also, we are trying to build the edge-auth as a generic container image for secure system communications in a microservice architecture. If you like building stuff and want to work on FOSS projects feel free to reach out and contribute to the project.