In this previous article we talked about how machine to machine authentication is managed through the OAuth 2.0 protocol, specifying the differences between authorization and authentication.
In this article we want to explain how we manage it internally through a specially created component, the Client Credentials, which uses the client registration and the Client Credentials flow of OAuth 2.0, and the Client Authentication methods client_secret_basic and private_key_jwt described in OpenID Connect 1.0.
The Client Credentials is a Mia-Platform service that we have implemented for the purpose of managing client registration, login, permissions and metadata.
Among the fundamental requirements there are:
- High performance: the service is contacted at each API call on the platform. Therefore, it should be written in a high performance language;
- Verification of the access-token validity must be done both internally to the platform and by external providers, who must also have access to the permissions of authenticated clients.
We have decided to implement the service using Go, a language already used in Mia-Platform for other projects and which we know to be performing.
Below we will describe the authentication methods supported by the service and the authorization phase, which is independent of the type of authentication that has been performed.
First of all, however, it is important to define how we decided to build the access token which is a central player in the OAuth 2.0 protocol, as we have explained in this article.
We have chosen to use the JWT (JSON Web Token) as a type of access token, which allows you to sign a payload containing a JSON with a variable number of claims. Claims are a common way for applications to acquire user information, such as the permissions of the client that requested authentication. Client Credentials creates the token signature using asymmetric keys, which are a public key and a private key.
For a more detailed explanation of JWT we recommend reading the respective RFC or visiting the jwt.io website.
Client Secret Basic
The first authentication method we developed is the basic method for grant type client credentials, called client secret basic.
This authentication method is based on sharing a client id and a client secret between the client and the Client Credentials service.
This pair is created during client registration.
As you can see from the image above, in order to register the user must send a name to the registration endpoint to identify the client.
The Client Credentials generates a client id and a secret, and saves them in a database reserved for credentials. Non-confidential client information, such as name, is saved in another database.
The Client Credentials then responds with the pair of credentials, adding the information about the expiration date and date of issue of the credentials, as per the OAuth2.0 RFC7591 specification.
The login endpoint is exposed under the /oauth/token route, and follows the OAuth2.0 specification for the grant type Client Credentials. The request is made using the x-www-form-urlencoded format and the credentials are sent in the authorization header with the Basic type. As you can see, the name of the authentication method comes from the location of the credentials in the authentication call.
The Client Credentials service verifies that the credentials are correct and responds to the client with an access token, information on the type of token (in our case, Bearer) and information on the duration (in seconds) of the token.
The duration of the access token must be temporally limited in order to secure the authorization: whoever manages to steal the token would not have time to use it.
To meet the different security needs of our customers, we decided to leave the duration of the access token configurable.
With the client secret basic method, the secret to login is sent. In this way it is possible to be exposed to attacks such as man in the middle aimed at obtaining the secret or to possible human errors (such as for example the sharing of an authentication curl with the credentials entered).
Private Key JWT
This authentication method considerably raises the security level of the mechanism. In fact, it appears to be among the methods allowed for the use of API Financial-Grade (FAPI): this means that it is suitable for protecting financial, banking and insurance interactions.
From an implementation point of view, the registration and login endpoints are the same as those used for Client Secret Basic. Only the parameters that are passed change.
This authentication method is based on the client having asymmetric keys.
As a first step, the client must create a pair consisting of a private key and its public key, and make the public key accessible to the Client Credentials service, sharing it via JSON Web Key (JWK), a JSON object representing a cryptographic key, such as explained in RFC 7517.
With this authentication method, the client does not share any secret with the authorization service, but only public information.
The following diagram illustrates the registration flow.
Below there is an example of a client's registration call using this authentication system and an RSA256 key:
curl --location --request POST 'http://client-credential-host/register' \
--header 'Content-Type: application/json' \
"client_name": "my client name",
"kid": "key id",
The public_key indicated in the body of the call is an example of JWK.
The Client Credentials service returns the generated client id and information on when it was generated.
Once the client id is obtained, the client can use the private key to obtain an access token.
To do this, the client must create a JWT signed with its own private key called client assertion. The client assertion contains information that allows Client Credentials to identify the client. In this way, Client Credentials can validate both the content of the JWT and whoever created it: the only one who has the private key is the client.
To ward off a Replay Attack, the JWT cannot be used more than once as an assertion method. Whoever comes into possession of the JWT assertion could not in any case use it. The possible human error of accidental credentials sharing was also resolved in this way.
The authentication request contains the following in the body (urlencoded format):
- grant_type: constant client_credentials;
- client_assertion_type: constant urn:ietf:params:oauth:client-assertion-type:jwt-bearer;
- assertion_jwt: the JWT signed with the private key held by the client.
This JWT must contain in the header the information on the public key to be used to verify its validity. Furthermore, in the payload, it must contain fixed claims:
- iss: the issuer, that is the clientId of the client that generates the JWT
- sub: the subject, that is the clientId of the client requesting the login
- aud: the identifier of the Client Credentials service
- jti: a unique identifier of the JWT
- iat: the moment of creation of the token
- exp: the expiration date of the token
- client id: the clientId for which the login request is being made;
- token_endpoint_auth_method: constant to private_key_jwt.
Use and validation of the access token
Once the access token has been obtained with one of the two authentication methods supported by Client Credentials, the client can use it to request authorization from the resource owner. This is done by inserting the access token in the request, within the authorization header.
If the requested service is provided by Mia-Platform, the validation is done by the Client Credentials itself.
One of the needs of Mia-Platform customers is to allow external systems to use Client Credentials as an authentication provider and system for assigning permissions. Through this mechanism, an external system will only need to verify the validity of the access token and the permissions contained in the claims.
Let's see the two mechanisms below.
Validation of the token through Mia-Platform
Once the request has reached Mia-Platform, the access token is extracted from the authorization header.
Client Credentials verifies the validity of the access token through the respective public key and extracts the claims of the JWT. Among these claims there are the permissions that the client has, and which can therefore be used to authorize the client based on the request.
The verification of the permissions, the authorization phase, can be done by Mia-Platform’s Authorization Service. The method by which authorization operations are carried out within Mia-Platform are beyond the scope of this article.
Validation of the token through an external client
As previously mentioned, the access token is a JWT signed using an asymmetric key. Client Credentials exposes a set of public keys (JWKS) through a dedicated endpoint.
An external provider to verify the access token has to:
- get the set of public keys;
- search for the respective public key via a key identifier contained in the access token (in the claim kid contained in the JWT header);
- validate the access token using the public key just obtained.
Once the JWT is validated, the external service extracts and uses the permissions to authorize, or not, access to the requested resource.
The access token has an additional claim, called audience, which contains an identifier of the system that owns the requested resource. When logging in, a client must specify the requested audience.
This claim allows you to avoid improper use of the access token by the recipient, as shown below.
Three systems A, B and C use the same authentication provider and have the following permissions:
- A has permission to access B and C
- B has no permissions to access C
If system A called system B with a token, nothing would prevent system B from using the same token to access system C, which it should not be able to access. Since in the login phase the client A specified B as the audience, therefore the access token can only be used to access B.
Since the audience is saved among the claims of the JWT, an external system can, and must, check if that token has been created to access that specific system.
There are several ways to manage M2M authentication. We have chosen to create a dedicated service, Client Credentials, which uses the Client Credentials flow of OAuth 2.0, and the Client Authentication methods client_secret_basic and private_key_jwt described in OpenID Connect 1.0.
By creating a specific component, in addition to freeing ourselves from third parties, we were able to calibrate the choices on our performance needs - using Go language - and security - by implementing jwt technology.
The result is a new platform component, secure and performing, which can be configured quickly and easily, with significant cost savings.
This article is written by Davide Bianchi, Senior Technical Leader, and Davide Tantillo, Senior Technical Leader.
© MIA s.r.l. All rights reserved