IntroductionIn this article, we will discuss how we will build the Authentication Service. This service is the most complex of all the services we will build and has several responsibilities:
- Registration-Registration captures the minimum information we need to create a user account. This includes the user's name, email and password.
- Authentication & Authorization-Authentication validates a user's credentials against the service's PostgreSQL database. When a user has successfully been authenticates, the user's assigned roles are retrieved and a JSON Web Token (JWT) is generated with the corresponding information as well as an initial refresh token.
- JWT Token Refresh-Initial Authentication & Authorization requires the user to supply their credentials. To avoid requiring the user to provide credential repeatedly, the initial JWT generates a refresh token which can be used to generate a new JWT without the user's credentials.
- Password Change- The service allows the user to change their password once they have been authenticated.
- Password Recovery Request-If a user is unable to authenticate with their credentials, a provision is made to allow the user to request a password recovery code be sent to the account's registered email.
- Password Recovery-Using the users email address and a password recovery code, a user is able to change their password without previous authentication. When successful, the new credentials can then be used to authenticate the user
- User Administration-In addition to the user-related feature, the service provides administrative features to retrieve user information as well as enable/disable particular users.
Understanding the Authentication Service
The Authentication Service is undoubtedly the most complex services we will build. This is due to the numerous functions it must perform. During the development of this service we will introduce several concepts that will appear in future services including, REST controller, Swagger, JWT authorization, data persistence, message queues, and Internationalization (18N).
additional InfrastructureIn addition to the services we have seen in previous articles, the Authentication Service employes two additional infrastructure elements:
- PostgreSQL database-The Authentication Service persists its credential data in a relational PostgreSQL database. The PostreSQL instance is deployed as a containerized service and does not require separate installation.
- RabbitMQ Message Broker-The Authentication Service published domain event to a RabbitMQ Message Broker which is shared across the application with other services. Rabbit MQ is deployed as a containerized service and does not require separate installation.
Users and RolesThe Authentication Service is built around the concept of Users and Roles. A User is identified by a set of credentials (email address, password) and is assigned a set of Roles. Each role represents a classification of privileges granted to the user. Each user can be granted zero or more roles. By default, the service will create a default administration user. This feature can be disabled by setting the configuration property admin.user.create to false.
User RegistrationA Registration feature allows new users to create an account. Registration creates a User record is assigned a default set of roles. When a new user is created, the service will publish a ACCOUNT_REGISTERED_EVENT to the RabbitMQ message broker.
JWTIn an earlier article on Microservice Authentication and Authorization, we discussed the use JSON Web Tokens (JWT) in the context of microservices applications. We will be employing JWTs for all of the secured feature of our applications services. Each JWT contains JSON-encoded data allowing the token to store information needed we wish to pass to other services (e.g., user subject, accountId, roles). Each token is cryptographically signed to ensure that the token hasn't been modified while in transit. This ensures that a MITM attack can't modify the token contents or escalate user privileges.
AuthenticationThe AuthenticationService provides an authentication feature which validates user credentials (e.g., email & password) and returns a JWT to the calling client. This JWT is then passed by the client with every service call. Each service is then responsible for ensuring the JWT is valid (e.g., token not expired) and that the caller has sufficient privileges to access the secured service feature by comparing the roles included in the JWT and the roles required by the service feature.
Refresh TokensThe generated JWT token expires quickly as a security precaution. A long-lived JWT could potentially be hijacked by a third party and used to access the application as the user. To avoid having the client user manually re-authenticate when the JWT expires, refresh token is embedded which is longer lived. The refresh token is used to generate additional tokens when the intial JWT expires without requiring the user to resupply their credentials. Refresh tokens can be used to generate fresh JWTs behind the scenes and not impact the client application's user experience.
Authorization FilterFor services with secured features, we will be using an Authorization Filter. The Authorization Filter implements the Intercepting Filter pattern and intercepts all calls to the service endpoints. The Filter tests the service request's URI path against a list of secured paths. Each secured path has an associated set of roles that must be present to authorize the service feature. If the callers JWT roles do not contain the required roles the filter will return an HTTP 401 UNAUTHORIZED response. If the JWT roles contains the required roles the service feature will be invoked. The Authorization Filter allows the service to centralize the authorization to be isolated from the individual service feature. All services in the reference implementation that contain secured features will be use an Authorization Filter to handle service authentication.
RepositoriesTo manage the Authentication Service's persistent entities,we will be using a Spring Boot Repository. A Spring repository encapsulate the entity's CRUD operations and handles the Object-Relational mapping for the developer. We will be building two repositories. The first is the User Repository which will manage User entities, and a Role Repository to store the application roles. The repository approach allows us to separate our persistence from our business logic.
ServicesAs we build our individual microservice applications, it is a best practice to separate our business logic from its callers, and the dependencies it calls. To do this we use the notion of the Spring Boot Service. Unfortunately, the term service is being heavily overloaded in these articles. In this context we use the term Service to identify the business logic of the microservice. Unsurprisingly, the AuthenticationService application will contain an AuthenticationService business logic service. So much for creative naming.
Domain Event PublishingThe Authentication Service application will publish various events onto the RabbitMQ message bus to inform other services that the service has performed some operation. To do this we will be using the Spring Cloud Stream framework. This framework enables us to build scalable, event-driven, message sharing system, to connect various services. The Spring Cloud Stream framework simplifies the process publishing and consuming messages and provides binder implementations for for RabbitMQ as well as other messaging systems.
ControllersWe now need a mechanism to expose the business logic functionality to the world. The Authentication Service (and several future services) will employ REST endpoints to enable external clients to communicate with the Authentication Service. To implement this we will be using the Spring Web MVC Framework, and specifically Spring REST controllers. Spring allows us to simply annotate a given class and its methods to expose those methods as REST endpoints. These controllers constitute the Service's exposed API.
Notification Service IntegrationThe AuthenticationService integrates with the Notification Service to send emails for registration and password recovery requests. This is accomplished using the EmailClient component which makes a REST call to the Notification Service. Under the covers, The AuthenticationService will query the Discovery Service to find and instance of the NotificationService and invoke the discovered instance. The EmailClient leverages Spring's RestTemplate facility to invoke the Notification Service.
- AuthenticationService Github repository
- AuthenticationService Docker hub image.
- ThinkMicroservice AuthenticationService Dashboard.