In the context of the "portail Data", we needed an authentication/authorization system in order to control and restrict the access of some ressources.
In the context of the Data portal, we needed an authentication/authorization system in order to control and restrict the access of some ressources.
There is an existing system that let a user authenticate and then access or not some restricted access dataset. However, it does not implement those security features in a modern and very secure way. For exemple, to get the resources accessible by a user we need to pass its username and password. Obviously we cannot just store and pass the user's unencrypted credentials everytime we need it. To address this problem, we developped a middleware that implements a more modern authentication system using features of our API gateway and the legacy service to generate JSON Web Token.
...
...
@@ -6,11 +6,11 @@ In the following sections we will explain in more details what as been implement
## Authentication
There are two kind of entities that can be authenticated: user and services.
There are two kind of entities that can be authenticated: users and services.
### Authenticating a user
Lets explain step by step what is going on from the user account creation to the when he/she lists its accessible resources.
Lets explain step by step what is going on from the user account creation to the when he/she/it lists its accessible resources.
```plantuml
@startuml
...
...
@@ -50,7 +50,7 @@ participant "Middleware Legacy Auth" as middle
@enduml
```
Before anything, the front web application has to retrieve the public encryption key from the middleware. It will use it to encrypt the password entered by the user when creating its account but also when logging in. This key is part of a public/private key generated by the middleware when it starts up.
Before anything, the front web application has to retrieve the public encryption key from the middleware. It will use it to encrypt the password entered by the user when he creates its account but also when he logs in. This key is part of a public/private key generated by the middleware when it starts up.
```plantuml
@startuml
...
...
@@ -94,9 +94,9 @@ end
@enduml
```
When a user has entered and submitted its information, the account is not directly created. We require the user to verify its email address. To do so the middleware stores temporarily the user information in redis store as a key/value pair. The key is a generated unique Uuid4 token. This key/value pair is valid for 24h.
When a user has entered and submitted its information, the account is not directly created. We require the user to verify its email address. To do so the middleware stores temporarily the user information in redis as a key/value pair. The key is a generated unique Uuid4 token. This key/value pair is valid for 24h.
then, the middleware formats an email containing a link to a particular page of the front web application that includes the Uuid token.
Then, the middleware formats an email containing a link, that includes the Uuid token, to a particular page of the front web application.
Finally it transfers the email to the email service that will handle the delivery job.
...
...
@@ -145,7 +145,7 @@ end
@enduml
```
When the user receives the "account validation" email, he/she is invited to click the link. This will open the web application that will get the Uuid4 token from the url and call a validate account endpoint of the middleware.
When the user receives the "account validation" email, he/she/it is invited to click the link. This will open the web application that will get the Uuid4 token from the url and call a validate account endpoint of the middleware.
If the token is still in Redis, then the middleware can process with the account creation calling the legacy Auth service.
...
...
@@ -200,15 +200,15 @@ end
@enduml
```
Once the account creation done, the user can log in with its credentials. The front sends the username and encrypted password to the authentication service.
Once the account creation is done, the user can log in with its credentials. The front sends the username and the encrypted password to the authentication service.
This authentication service will then start a process of couple steps:
This authentication service will then start a process of a couple steps:
* Verify the user identity transfering the credentials to the middleware which will unencrypt the password and call the legacy authentication service to get user info. If the credentials are correct the authentication service will receive the information of the user profil.
* Once the profile verified, the authentication service will create the user identity in our API Gateway (Kong) (if not already existing). This is referenced as a [consumer](https://docs.konghq.com/0.14.x/admin-api/#consumer-object) in the API Gateway documentation.
* Based on the created consumer and thanks to the [JWT plugin](https://docs.konghq.com/hub/kong-inc/jwt/) of Kong, the authentication service will get the JWT Credentials of the user. Those are per user credentials containing a public and private key. If the user does not have credentials yet the authentication service will generate them using the appropriate endpoint of the Kong JWT plugin.
* The service generates a unique random Uuid4 that we will call the xsrf token
* At this point, the authentication service get everything it needs to generate authentication pieces for the user. Using the credentials coming from Kong it will sign a JSON Web Token containing some of the User info (firstname, lastname, username, email) as well as the user encrypted password and the xsrf token.
* Finally the authentication service will return a response to front application with the xsrf token and the user info in the body. It will also set a cookie `access_token` containing the JWT for the domaine name hosting the application.
* Once the profile verified, the authentication service will create the user identity in the API Gateway (Kong) (if not already existing). This is referenced as a [consumer](https://docs.konghq.com/0.14.x/admin-api/#consumer-object) in the API Gateway documentation.
* Based on the created consumer and thanks to the [JWT plugin](https://docs.konghq.com/hub/kong-inc/jwt/) of Kong, the authentication service will get the JWT Credentials of the user. Those are per user credentials containing a public and private key. If the user does not have credentials yet, the authentication service will generate them using the appropriate endpoint of the Kong JWT plugin.
* The service generates a unique random Uuid4 that we name "xsrf token"
* At this point, the authentication service has everything it needs to generate authentication pieces for the user. Using the credentials coming from Kong it will sign a JSON Web Token containing some of the User info (firstname, lastname, username, email) as well as the user encrypted password and the xsrf token.
* Finally the authentication service will return a response to the front application with the xsrf token and the user info in the body. It will also set a cookie `access_token` containing the JWT for the domaine name hosting the application.
```plantuml
@startuml
...
...
@@ -306,7 +306,7 @@ end
@enduml
```
When a user access the data accesses page, the web application makes a call to the middleware through the API gateway: Kong. The JWT plugin of Kong will verify that the request has a valid JWT (not expired, not modifed) in the dedicated cookie. If the failing case, Kong will return a `401` http error, otherwise it will proxify the request to the authentication service.
When a user navigates to the data accesses page, the web application makes a call to the middleware through the API gateway: Kong. The JWT plugin of Kong will verify that the request has a valid JWT (not expired, not modifed) in the dedicated cookie. In the failing case, Kong will return a `401` http error, otherwise it will proxify the request to the authentication service.
Each endpoints that requires a user identity are protected by a "middleware". You can find the corresponding code in the `decode-jwt-payload.middleware.ts` file of our projects. The main role of this application middleware is to decode the JWT payload in order to get the user identity (username, encrypted password).
...
...
@@ -316,21 +316,21 @@ However in order to get through the middleware and go on with the incoming reque
* the xsrf-token present in the header is the same as the one present in the JWT payload
If one or more of those verifications fails the middleware will return a `401 Unauthenticated` error.
If the get user resources request pass successfully those tests, the legacy middleware authentication will call the Legacy auth service and return the user resoures.
If the get user resources request passes successfully those tests, the legacy middleware authentication will call the Legacy auth service and return the user resoures.
### Authenticating a service
In a micro-services architecture, it is very common that a particular service needs another one to accomplish a task. There could different way to implement this depending on the needs. In our most common cases the first service needs are direct response of the second service. Thus, we decided to use HTTP requests. Just as the web application calls a service through the api gateway, a service calls another one through the proxy of the API Gateway.
In a micro-services architecture, it is very common that a particular service needs another one to accomplish a task. There could different ways to implement this depending on the needs. In our most common cases the first service needs are direct response of the second service. Thus, we decided to use HTTP requests. Just as the web application calls a service through the api gateway, a service calls another one through the proxy of the API Gateway.
Some HTTP endpoints might be secured and the service will need to authenticate itself to the other service. Obviously a service can't log in with its own credentials as a user would do. The authentication method for services is consequently a bit different and based on API Keys which is another authentication method supported by Kong.
Some HTTP endpoints might be secured and the service will need to authenticate itself to the other service. Obviously a service can't log in with its own credentials as a user would do. The authentication method for services is consequently a bit different and based on API Keys which is another authentication method supported by Kong.
Each service that need to access protected endpoints will need to have its own key. You can use Kong admin API or [Konga](https://pantsel.github.io/konga/) that provides a GUI for the management of Kong in order to generate keys. In the following I will explain how to proceed using Konga interface.
Each service that needs to access protected endpoints will need to have its own key. You can use Kong admin API or [Konga](https://pantsel.github.io/konga/) that provides a GUI for the management of Kong in order to generate keys. In the following paragraph I will explain how to proceed using Konga interface.
An API key is associated to a consumer, so the first step is to create a consumer that will represent the service. As a convention we decided to prefix the services related consumers by '__' which gives for example `__legacy-auth-middleware`. In the consumers section, we can create a new consumer and enter the username of our choice.
Once the consumer is created, when clicking on its name, we can management its credentials, its groups and a couple more things. In the credentials section and under the API KEYS tab we can create new keys for this specific user. After clicking the create button, juste leave the key field empty if you want to let kong generate it for you.
Once the consumer is created, when clicking on its name, we can manage its credentials, its groups and a couple more things. In the credentials section and under the API KEYS tab we can create new keys for this specific user. After clicking the create button, juste leave the key field empty if you want to let kong generate it for you.

...
...
@@ -340,7 +340,7 @@ Let's say we want a global plugin. In the plugin section, click the "add global
So now the service just need to pass a header `ApiKey:<the service api key>` when calling another service through kong to pass its identity.
So now the service just needs to pass a header `ApiKey:<the service api key>` when calling another service through kong to pass its identity.
However specifying one by one which service is allowed to access a service endpoint is not very convenient, we were more interested in a group based approach. For example our authentication-middleware could belong to a `email-writter` group which would allow it to give send mail tasks to the email service.
...
...
@@ -348,21 +348,21 @@ Thankfully kong consumers already support this functionnality. We will explain i
## Authorization
In the previous section we have seen how the identity of a user (or a service) is pass to a service through the API Gateway and how it can use it. Knowing the identity of the consumer was not sufficient in some cases. If I take the example of the organization service, we didn't really care about the specific identity of the user but we wanted to know whether the user was an admin or not: a group based authorization.
In the previous section we have seen how the identity of a user (or a service) is passed to a service through the API Gateway and how it can use it. Knowing the identity of the consumer was not sufficient in some cases. If we take the example of the organization service, we didn't really care about the specific identity of the user but we wanted to know whether the user was an admin or not: a group based authorization.
First thing first let's see how to add a group to a consumer. Find the consumer in the list and click on it to see the details. Navigate to the group section. Click the add group button and enter the name of the group. The consumer now belongs to group. This works fboth for user related consumers or service related consumers.
First thing first let's see how to add a group to a consumer. Find the consumer in the list and click on it to see the details. Navigate to the group section. Click the add group button and enter the name of the group. The consumer now belongs to a group. This works both for user related consumers or service related consumers.
Then, we need to configure an Access Control List plugin in order for kong to pass the user groups to the upstream. The ACL plugin needs an authentication plugin (JWT plugin, key-auth plugin...) to have indentified an existing user to then get its groups. Go to the global plugin section, click the add plugin button and select `Acl` under security. We don't really want to whitelist or blacklist user here we just want to propagate the user's groups to the upstream server. As the plugin require one of the two fields to be filled, we basically just enter a fake user id under blacklist.
Then, we need to configure an Access Control List plugin in order for kong to pass the user groups to the upstream. The ACL plugin needs an authentication plugin (JWT plugin, key-auth plugin...) to have indentified an existing user to then get its groups. Go to the global plugin section, click the add plugin button and select `Acl` under security. We don't really want to whitelist or blacklist users, here we just want to propagate the user's groups to the upstream server. As the plugin require one of the two fields to be filled, we basically just enter a fake user id under blacklist.
At this point if a service receive an authenticated request it will find the user/service's groups in the following header: `x-consumer-groups`.
Our services are developed using the frameword Nest.js. This framework comes with a concept call guards. A guard is basically a middleware that intercept the request and that will let it through only if it respect some conditions. In our case we need a group guard that would return a `403 Forbidden` error if the user does not belong to the required group.
Our services are developed using the framework Nest.js. This framework comes with a concept called guard. A guard is basically a middleware that intercepts the request and that will let it through only if it respects some conditions. In our case we need a group guard that would return a `403 Forbidden` error if the user does not belong to the required group.
The corresponding code can be found in the `groups.guards.ts` file of the project that required this king of authorization (ex: email service). In order for this guard to be applied to our service, we declared it as a provider in the `app.module.ts` file as following.
The corresponding code can be found in the `groups.guards.ts` file of the project that required this kind of authorization (ex: email service). In order for this guard to be applied to our service, we declared it as a provider in the `app.module.ts` file as following.