Implement Server Stub

Open auth-service/src/main/java/com/example/auth/grpc/AuthServiceImpl.java. In this file, we need to:

  1. Extend from the gRPC generated AuthServiceBaseImpl class
  2. Implement the authenticate and authorization methods

Extend the Base Implementation

...
// TODO Extend gRPC's AuthenticationServiceBaseImpl
public class AuthServiceImpl extends AuthenticationServiceGrpc.AuthenticationServiceImplBase  {
  ...
}

Override and Implement the Authenticate and Authorize Methods

The base implementation bootstraps the underlying descriptors and pipes necessary for gRPC server to communicate with the actual service implementation, and makes the call to the actual service methods. The service methods have default implementations in the base class, but they will all throw Status.UNIMPLEMENTED error. You’ll need to override these methods explicitly:

Override Authenticate

Override the authenticate() base implementation method

// TODO Override authenticate method
@Override
public void authenticate(AuthenticationRequest request, StreamObserver<AuthenticationResponse> responseObserver) {
...
}

Notice that even though we defined a simple unary call (request/response), the server implementation is fully asynchronous. That means, to return the value or errors to the client, you must use responseObserver.

  1. Use UserRepository to retrieve the user based on the username.
User user = repository.findUser(request.getUsername());
  1. If user doesn’t exist, return Status.UNAUTHENTICATED error
  2. responseObserver.onError(…) will close the stream. You should return and avoid calling any other responseObserver callbacks afterwards.
  3. Similarly, if user exists, but the password doesn’t match, also return Status.UNAUTHENTICATED error using responseObserver.onError(…)
if (user == null || !user.getPassword().equals(request.getPassword())) {
  responseObserver.onError(Status.UNAUTHENTICATED.asRuntimeException());
  return;
}
  1. Finally, if all things goes well, generate a JWT token and return it to the it. You must call responseObserver.onCompleted() to completed the call. Otherwise, the client will wait until that is called. If you never call it - then the client will wait forever.
String token = generateToken(request.getUsername());
responseObserver.onNext(AuthenticationResponse.newBuilder()
  .setToken(token)
  .build());
responseObserver.onCompleted();

Override Authorize

Override the authorize() method:

// TODO Override authorization method
public void authorization(AuthorizationRequest request, StreamObserver<AuthorizationResponse> responseObserver) {
  ...
}

Use jwtFromToken(…) to verify the token

DecodedJWT jwt = jwtFromToken(request.getToken());

Catch JWTVerificationException:

try {
  DecodedJWT jwt = jwtFromToken(request.getToken());
} catch (JWTVerificationException e) {
  responseObserver.onError(Status.UNAUTHENTICATED.asRuntimeException());
  return;
}

If JWT token is valid, a DecodedJWT object will be returned. From there, extract the username using getSubject()

String username = jwt.getSubject();
User user = repository.findUser(username);

if (user == null) {
  // send error
  return;
}

Use UserRepository to retrieve the user, construct AuthorizationResponse with the roles, then return that.

responseObserver.onNext(AuthorizationResponse.newBuilder()
  .addAllRoles(user.getRoles())
  .build());
responseObserver.onCompleted();

Hint: Full implementation here