Accueil > OpenID Connect OAuth Server by DnC > Develop > OpenID Connect > Json Web Token (JWT)

When using OpenID Connect protocol flows, an application can obtain an ID Token in JWT format, in parallel with the normal opaque token.

Signed cryptographically, reliably the token binds the identity of the application, the end user, and the authorization scopes.

After describing the JWT token, we indicate how to check and consume it.

More information in the french version

Verifying the origin of the request received by a resource server

  publié le par DnC

The transmission of tokens to protected resources implies that the latters are receptive to tokens of any origin. This presents an opportunity for an attacker (a foreign application) to exploit a compromised or stolen token.

Once more, the analysis reveals that we are only able to ensure data security in the case of the "Authorization Code" flow.

Consideration about the security of token transmission to resource servers (RS)

The proposed RFC 6749 standard is quite clear on the need to use access tokens only between authorized parties (§ 10.3.) :

Access token credentials (as well as any confidential access token attributes) MUST be kept confidential in transit and storage, and only shared among the authorization server, the resource servers the access token is valid for, and the client to whom the access token is issued.

If all the resource servers (RS) to which the tokens are transmitted belong to the same organization (corporate realm), we can hope that the tokens will not be compromised, that is to say accessible to applications outside the organization. It can also be assumed that the resources are located in a subnet protected by a firewall that prohibits application requests from outside the subnet. It can then be assumed that any application directed to the resources is allowed to receive a response.

This is called "trusted applications". But note that, from the point of view of a resource server, trust does not result from a particular character of the application, but from the fact that it is located inside a space of confidence that we assume inaccessible to other applications.

However, even in this case, a well-crafted attack could lead to stealing the token and exploit it with a foreign application.

In any case, the assumptions are never very good in terms of security.

Of course, if you address a token to a foreign resource, you must consider the token as a compromise because it can be used by a foreign application.

Rather than rest on a notion of confidence area and trustable applications, the resource server must be able to verify the legitimacy of the token holder.

But how ?

The specification clearly describes this limitation (section 10.3) :

This specification does not provide any methods for the resource server to ensure that an access token presented to it by a give client was issued to that client by the authorization server.

Two types of applications must be distinguished with regard to the origin of the request :
- applications "with back-end", ie sending their requests from a server : the request will be sent with the IP of the application server.
- The applications without back-end, issuing their request from a user-agent (the browser of the end user, the operating system of the host computer of a native application etc. .) : the request will be issued with the IP of the user-agent connection, possibly masked by a proxy.

In the current state of development of OAuthSD, we will only consider "back end applications" whose IP is related to their identity. In other words, we are only able to ensure data security in the case of the "Authorization Code" flow.

OAuthSD offers two ways to verify the legitimacy of a "back-end" application presenting the token based on the IP of origin :
- by introspection with the IP of the applicant,
- with the additional declarations of the JWT token ’cip_hash’ or ’cip’.

The IP of the client application, to which the IP of the applicant will be compared, are determined as follows :
- if the client application is registered with a non-empty ’ip’ field of the ’clients’ table, OAuthSD will use this value,
- Otherwise, the IP (s) will be obtained by querying the DNS with the host name of the return URL (redirect URI) registered for the application [1] [2].

Check by introspection with the IP of the applicant

The draft RFC 7662 implicitly recognizes the question, but does not give a standardized answer. It is indeed indicated in section 2.1 :

The introspection endpoint MAY accept other OPTIONAL parameters to provide further context to the query. For instance, an authorization server may desire to know the IP address of the client accessing the protected resource to determine if the correct client is likely to be presenting the token. The definition of this or any other parameters are outside the scope of this specification, ....

OAuthSD do the following :
If the RS calling the Introspection endpoint transmits the IP address of its own requestor using the ’requester_ip’ parameter, introspection verifies that this IP address is in the same subnet as the client application identified by the ’aud’ declaration.

In order to avoid responding to a malware a protected resource receiving an identity token must validate it by passing the parameter ’requester_ip’.

On OAuthSD, for this verification to be effective, it is necessary to :
- fill in the IP of the client applications when they are registered (otherwise the domain IP of the return URI will be used),
- set to ’true’ the configuration constant CHECK_CLIENT_IP.

Notes :
- In general, the IP of the applications using the Introspect or UserInfo controllers is verified as indicated above, without being necessary to provide the ’requester_ip’ parameter, in order to verify that it is well of the registered client application. It will be understood that in the case of a request made to a resource server that calls the Introspect controller, it is necessary to provide the IP of the applicant, otherwise it would be the IP of the resource server that would be monitored, not the one of the requester.
- Note that there is also an optional verification of membership in the same domain or subdomain (see configuration constant CHECK_SAME_DOMAINS) performed by the Introspect controller.

Examples of calling introspection with the ’requester_ip’ parameter

By header :

PHP

  1. // Method Bearer + parameters by Post
  2. $data1 = array(
  3.     'requester_ip' => $_SERVER['SERVER_ADDR'],  
  4. );
  5. $authorization = "Authorization: Bearer ". $res1['id_token'];
  6. $h = curl_init($introspection_endpoint);
  7. curl_setopt($h, CURLOPT_HTTPHEADER, array(
  8.     'Content-Type: application/x-www-form-urlencoded',
  9.     'Authorization: Bearer ' . $res1['id_token']
  10. ));
  11. curl_setopt($h, CURLOPT_RETURNTRANSFER, true);
  12. curl_setopt($h, CURLOPT_POST, 1);  
  13. curl_setopt($h, CURLOPT_POSTFIELDS, http_build_query($data1));
  14.  
  15. $res = curl_exec($h);
  16.  
  17. ...

Télécharger

By Post :

  1. // Post Methode  
  2. $data1 = array(
  3. 'token' => $res1['id_token'],
  4. 'requester_ip' => $_SERVER['SERVER_ADDR'],  
  5. );
  6.  
  7. $trace = $_SESSION['trace'];
  8. $trace .= '----- Step 3 : JWT Introspection -----' . "<br />";
  9. $trace .= 'access token : ' . $access_token . "<br />";
  10. $trace .= 'data : ' . print_r($data1,true) . "<br /><br />";
  11. $_SESSION['trace'] = $trace;    
  12.  
  13. $h = curl_init($introspection_endpoint);
  14. curl_setopt($h, CURLOPT_RETURNTRANSFER, true);
  15. curl_setopt($h, CURLOPT_TIMEOUT, 100);    //100 : DEBUG
  16. curl_setopt($h, CURLOPT_POST, true);
  17. curl_setopt($h, CURLOPT_SSL_VERIFYPEER, false);
  18. curl_setopt($h, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));  
  19. curl_setopt($h, CURLOPT_POSTFIELDS, http_build_query($data1));
  20.  
  21. $res = curl_exec($h);
  22.  
  23. ...

Télécharger

See also :
- API Open ID Connect : Introspection,
- More about Introspection,
- Envoi d’une requête à un serveur de ressource (RS).

Check with the JWT token ’cip_hash’ or ’cip’ additional claim

OAuthSD introduces a ’cip’ or ’cip_hash’ claim into the payload of the JWT token to enable verification of the IP of the applicant [3] [4].

This verification can be done locally, or by introspection called as described above with the parameter ’requester_ip’.

’cip_hash’
In the general case, an application has only one IP. In this case, it is this claim that will be generated, and not ’cip’, with the advantage of hiding the IP of the client application by transmitting it in the form of a hash. If the application has multiple IPs, and the FORCE_CIP_HASH configuration constant is true, the first IP in the list will be used to generate the declaration.

’cip’
The ’cip’ claim is generated when the IPs of the client application are multiple. Its value is a suite of space-separated IPv4.
However, if the FORCE_CIP_HASH configuration constant is true, the ’cip_hash’ claim will still be issued instead.

Notes :
- One of these additional claims will always be added to the JWT identity token payload of the OpenID Connect Authorization Code stream, but not in its OAuth 2.0 counterpart.
- This method has the advantage of allowing verification by the protected resource (remember that the payload of the JWT token is simply URL64-encoded ). This is useful for blocking an attack without introspection, or when validation of the JWT token is done locally without introspection.
- The Introspect controller will also perform IP verification with these claims. In the case of a resource server, it is necessary to transmit the parameter ’requester_ip’.
- In principle, there should not be a proxy in front of an authentication server, which must have access to the network (including its own router-firewall). Any proxy located between the network and the authentication server must faithfully relay the IPs. Of course, reverse proxies are prohibited in front of an authentication server.

Taking into account a "trusted proxy"

The question is whether one can trust the HTTP_X_FORWARDED_FOR declaration by which a proxy retransmits the IP of the original request.

Any proxy located between the Internet and the authentication server is controlled by the organization and can be considered trusted.
OAuthSD allows to designate these proxies by their IP in the configuration constant TRUSTED_PROXIES. If the USE_PROXY configuration constant is true, OAuthSD will replace for IP verification these IPs by the one provided by the HTTP_X_FORWARDED_FOR declaration.

Notes :
- Among the trusted proxies we can find the reverse proxies whose function is to provide a cache system. The very principle of authentication excludes the use of cache.
- Among the trusted proxies are also load balancers. Some of these proxies do not have a fixed IP. In this case, it will be impossible to apply the client verification by IP.
- It will be necessary to verify that the trusted proxy only retransmits the real IP of the request by the declaration HTTP_X_FORWARDED_FOR, unless an identical mechanism makes it possible to recognize the trusted upstream proxies.
- Note that this concept of trusted proxy is the opposite of what a large number of Internet users refer to as the proxy they expect to hide their IP address. Such proxies call themselves "anonymous" and are referred to as "private proxy", which has nothing to do with the fact that they are private, but that they aim to ensure the anonymity (privacy) of the user.
OAuthSD has a "SAFEIP" feature to block questionable IP addresses, most of which are those of such proxies. We are in the presence of two complementary mechanisms : The taking into account of trusted proxies allows to authorize the proxies of the LAN / Web interface, while the "SAFEIP" function will block the suspicious proxies of the Web.

Notes

[1It is therefore important to ensure the integrity of the DNS. It would be best if the DNS of the applications and the DNS to which the server is accessing (preferably a local DNS) are in a trusted space, and / or protected by DNSSEC.

[2This method can introduce an unwanted response time of the resource, which is why OAuthSD offers the registration of the IP.

[3We extol the interest of the signed JWT identity token to link the identity of the end user, the application and the authorization scopes. However, a HTTP resource server can only identify the origin of the request by its IP in the server-server relationship. The identifier of the client transmitted by the token is not convincing since the token may have been "stolen". It is therefore essential to incorporate in the JWT token the IP of the legitimate application so that a resource server can check it.

[4Can the ’aud’ declaration be used to transmit this URL ? This seems to be the case if we stick to the JWT specification for which ’aud’ can be a list of URIs. But the OpenID Connect is more restrictive :

aud
MANDATORY. Audience (s) for which this identifier token is intended. It MUST contain the client_id OAuth 2.0 of the trusted party as the audience value. It MAY also contain identifiers for other audiences. In the general case, the aud value is an array of case-sensitive strings. In the usual special case where there is an audience, the aud value MAY be a case-sensitive string.

As a result, the oauth2-server-php library sets the value of the ’aud’ parameter to the identifier of the client application. The ’aud’ declaration is not the right choice for OAuthSD to propagate client IP.

Add additional claims to the JWT token

  publié le par DnC

The OAuth 2.0 specifications allow the addition of new claims to the payload of the JWT token.
This article shows how this is done within the framework of OAuthSD.

Use case

It should be ensured that the end-user is well able to access protected resources, whether personal data or the application owns the data. We also want to be able to control the data transmitted by a protected resource to an application based on the rights of the end user in this application.

Administrators as well as end users will use OpenID Connect to authenticate to an application. We will want to modulate the rights and privileges of the end user according to his identity, his membership or his profile. The simplest example is the distinction between data read rights and edit rights. One can also consider assigning the user administrative rights to all or part of an application.

This is not the role of the authentication server, which must be transparent with respect to the authorization scopes implemented by an application. For this, OAuthSD allows an external application to incorporate additional declarations to JWT tokens, whether it is a JWT-format access token or an identity token. Protected resources "will know how to" interpret JWT data to control their response.

Note on data security

Recall that the payload of the JWT token is not encrypted, but only "URL64-encoded". Therefore, sensitive data should not be transmitted in this way.

The advantage of passing additional data into the payload of the JWT token is to unmistakably bind them using the signature to the identity of the client and to the user. This is particularly relevant for passing the privileges of a user or application to a protected resource. With this in mind, we can :
- directly transmit the privileges ; for example a role that would take values ​​such as ’administrator’, ’editor’, ’owner’, ’user’, etc., or any other code understood by the resource,
- or transmit the user information that will allow the protected resource to establish its privileges.

But be careful in this second case : it should not reveal sensitive data on the user. If transmission of such data to the protected resource is required, the protected resource can query the Userinfo service.
One could think of an encrypted token (JWE), but it would require, besides a specific development, to deviate from the standard OpenID Connect. It would be much simpler to encrypt the additional information in one block and pass it into a single statement.

Passing end-user privileges with authentication

OAuthSD offers two complementary methods for incorporating information about an end user into the JWT :

- writing additional data in the ’profile’ and ’scope’ fields of the users table using HTTP Rest service. This is an asynchronous process because the data is written at a time chosen by an external application, regardless of the authorization server query.

OAuthSD uses the scope ’privileges’ to control access to this information. When an application presents this scope in the authorization request, the ’profile’ and ’scope’ declarations are entered in the Userinfo response and in the JWT token payload.

- Incorporating additional data refreshed at the time of manufacture of the JWT token, for example by querying an external service. It is a synchronous process or "dynamic scope", to obtain information in phase with those held by the user management applications in the case where this management is external to the server.

This second method is developed now.

Incorporation of fresh additional data in JWT payload

This feature is specific to using OAuthSD in a group of applications controlled by the same organization (Corporate Realm).

The oauth2-server-php library provides a reconfigurable function for inserting additional declarations into a JWT at the time it is created.

To incorporate additional data, you must :
- Set the EXTRA_PAYLOAD configuration constant to ’true’.
- define the contents of the private_payload () function of the file /commons/oidc/interface/privatepayload.php.

The private_payload () function must be written by the application designer. It can be a call to a third party web-service. The function receives the parameters client_id, user_id and scope and must return a ’claim’ => ’value’ array which will be combined with the payload declarations without replacing the standard declarations.

Notes :
- This method allows you to create declarations with any name. However, the OIDC standard declarations (’id’, ’jti’, ’iss’, ’aud’, ’sub’, ’exp’, ’iat’, ’token_type’ and ’scope’) can not be overloaded : they will keep their name and their value. For instance, if a ’scope’ declaration is returned, it will be incorporated into the JWT payload as "extra_scope".

- The information is refreshed when the JWT token is created or refreshed. This implies that this information is not strictly synchronous with its corresponding in the external system. If a user’s rights change in the external system, it will be necessary to revoke the user’s session in order to trigger a new authentication request which will result in the creation of a new JWT token. But in the case where it would be necessary to change a large number of users, another way to ensure synchronism will be to close the service over the life of a JWT.

- This method does not write in the users table and, in particular, in the "scope" field. If the additional declarations include a "scope" declaration (which will usually be the case), the value of the "scope" field of the users table will be incorporated into the JWT payload under the name "user_scope".

- In any case, the value of the ’scope’ parameter passed to the authorize call by the application will be embedded under the name ’requested_scope’ [1].

See under the code for an example that retrieves additional data from a Web service.

Il existe des informations complémentaires, connectez vous pour les voir.

Notes

[1The name "scope" is unfortunately used by Oauth 2.0 for different concepts. It is therefore natural to use different names to avoid mixing or overwriting data.

Issuing Access Token as JWT

  publié le par DnC

The Brent Shaffer’s oauth2-server-php Library allows issuing Access Token as JWT.
With this feature, OAuth 2.0 and OpenID Connect authorization code flows look very similar, however ...

See : https://bshaffer.github.io/oauth2-server-php-docs/overview/jwt-access-tokens/

Configuring the Authorization Server for JWT Access Token

Inside the librarie, this is done by setting configuration parameter "use_jwt_access_tokens" to ’true’ :

  1. $config = array(
  2.     ...
  3.     'use_jwt_access_tokens' => true,
  4.     ...
  5. );

Télécharger

For the configuration of OAuthSD, the file /commons/configure_oauth.php and configure_oidc.php both define the USE_JWT_ACCESS_TOKENS constant, set to ’false’ by default.

With OpenID Connect flows, when configured to ’true’, the Token Controller issues both Access Token and ID Token as JWT.

  1.   access_token: string = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpZCI6ImYzNDExNjM3Mjc2ZjJjMzUxZjczYz
  2. ...
  3. VFx1Rk972S4ON1Dn6FwadTDS2U_A"
  4.   expires_in: long = 3600
  5.   token_type: string = "bearer"
  6.   scope: string = "openid profile"
  7.   id_token: string = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJvMnNwLmRuYy5nbG9iYWwiLCJzdW...
  8. m6TFdfGhgjPyIA0w"

Télécharger

Use Case

Listen to Brent Shaffer [1] :

"you can use JWT Access Tokens if you have distributed systems, and need to validate a token’s authenticity by more than one party without having to make a network call.

For instance, a token is granted by the Authorization Server. That token is a JSON Web Token signed by the Authorization Server’s private key. The Resource Servers (where API calls are made) are spread out all over the world, running multiple applications. As long as the Resource Servers have the Authorization Server’s public key, which does not need to be secured, they can validate the tokens quickly without any network calls. The tokens don’t even need to be persisted.

It’s an enterprisey use case, but is very useful for distributed systems."

Discussion

This feature has been defined in 2014, at a time the OpenID Connect was under construction. It probably aimed giving OAuth2 users the capacity to validate access tokens in resource servers.

We must also consider the problem posed by OAuth : it is not an authentication system (but authorization) because the access token is opaque and does not give information on the end user. By incorporating the user ID into the JWT token, OAuth is made an authentication system that passes user information to protected resources. That’s what Facebook does with what they call the "signed request".

With this feature, OAuth 2.0 and OpenID Connect authorization code flows look very similar.
However, we must note the difference between access token and identity token : they are not related in time. The notion of "OIDC" session is based on the validity of the identity token, that expires, while the access token expires independently, can be refreshed, or may not expire. This use of the access token as JWT can not replace the OpenID Connect identity token.

JWTs can be used as OAuth 2.0 Bearer Tokens to encode all relevant parts of an access token into the access token itself instead of having to store them in a database. This could be very valuable with session-less applications (like single page application), with a reduced latency of token validation.

More about Introspection

  publié le par DnC

In the absence of a stable standard, the Brent Shaffer’s oauth2-server-php library lacks an Introspection Endpoint.

OAuthSD, like many Authorization Servers, implements its own OpenID Connect Introspection controller. It has been developed "above" the library.

What could be a "minimal and compatible implementation" of the Introspection Controller for the library ? Before going into coding, let’s examine the RFC 7662 OAuth 2.0 Token Introspection Draft.

About the necessity for the Resource Server to authenticate

According to rfc7662 Section 2.1.,

"the endpoint (?) MUST require some form of authorization to access this endpoint".

( What we could have done to comply with this specification :
This implementation is waiting for a bearer access token issued for a registered client.
Where this access token is coming from is depending upon the service.
It may have been obtained from the server by the caller as a client application.
Or, if the caller is a resource server queried by a client application, it may have been passed to the caller by the application.)

We submit to the reader’s sagacity the following observations :

- The purpose of this authorization is "To prevent token scanning attacks".
This kind of attacks are usually mitigated at the firewall level in relation with the service.
For instance, with Apache, we could set a HTTP Basic authentication at the directory level.
Then, on a WHM/cPanel managed server, we could use CSF/LFD to block repetitive login failure.
We could also configure a particular Apache Modsec rule for that purpose.
More generally, mitigation of attacks is better performed upstream than at the application level. It induces a lesser or no computation load on the authorization server.

- For the resource server to authenticate itself, it must be registered as a client application [1], which assumes that it belongs to the corporate realm.
This is true in most cases, but we may wish that any foreign resource server can check JWT.

About the purpose of Introspection

Validate the signature
The payload of the JWT token is not encrypted, only encoded. Its content is immediately readable by the RS before validation of the signature.
So, the RS needs introspection to verify that the signature is valid (if not validating it locally using the public key).

Authenticate the application querying the RS
Furthermore, the purpose of passing a token to a RS is to tell him he may answer to its requester and what data it can pass in its response.
An attacker could have stolen a JWT and use it with his own application. The risk is having a RS respond to an unauthorized application and/or user. It is therefore essential to authenticate the application querying the RS in order to prevent responding to a malware (see below).

This said, what specific task should be completed by Introspection and what must be in the response ?

About Introspection response

According to section 2.2. from rfc7662, the only required member is "active", a boolean flag indicating the validity of the token seen from the server that issued it.
It’s more than just validating the signature because it "indicates that a given token was issued by this authorization server, was not revoked by the resource owner, ...".

But including in this value the condition "and is in its given validity time window" does not seem irrelevant : the RS already has the information of the payload. On the other hand, the token could have been invalidated at the server level, and it is this information that introspection will bring. In passing, note that there is no link between the lifetime of the access token and that of the identity token. There is a possibility to revoke the access token, but what about the identity token ?

Note that Introspection should also be usable to validate a JWT access token, with fewer and different members. that’s another reason for not authoritatively specifying members.

Preventing the Resource Server responding to a malware

That is the central question !

The specification recognized the question, but don’t give a normalized answer. It is stated in section 2.1. :

"The introspection endpoint MAY accept other OPTIONAL parameters to provide further context to the query. For instance, an authorization server may desire to know the IP address of the client accessing the protected resource to determine if the correct client is likely to be presenting the token. The definition of this or any other parameters are outside the scope of this specification,..."

We propose to implement it this way :
If the Resource Server calling the Introspection endpoint is passing the IP of its own requester in the ’requester_ip’ parameter, we must check that this IP is in the same subnet that the client application identified by the ’aud’ claim. See : Verifying the origin of the request received by a resource server.

About adapting the response to the Resource Server...

It is stated in section 2.2. :

"The authorization server MAY respond differently to different protected resources making the same request. For instance, an authorization server MAY limit which scopes from a given token are returned for each protected resource to prevent a protected resource from learning more about the larger network than is necessary for its operation."

It seems very difficult to implement. In the given example, dealing with scopes, the RS must call introspection with more parameters, or the AS should have information about the RS.

We think that the scopes defining the rights have a meaning that should be interpreted by the protected resource itself, the authentication server having to transmit them transparently.

This prescription can be applied in an enterprise domain for particular applications, so it will not be considered in our "minimal and compatible implementation".

... or not responding at all

It is stated in section 4. :

"If the token can be used only at certain resource servers, the authorization server MUST determine whether or not the token can be used at the resource server making the introspection call".

This prescription is nothing more than a radical variation of the previous one. We will treat it the same way.

Proposal of an Introspection Controller for the library

A "minimal and compatible implementation" of the Introspection controller would have following characteristics :
- will be limited to validating the JWT signature with the option of client IP checking (’requester_ip’ parameter), making no assumption of what the token is used for,
- will not necessitate authentication of caller,
- will accept Bearer token preferably to posting token.

 Follow this work on GitHub
See the fork :
Add more commits by pushing to the introspection branch on https://github.com/bdegoy/oauth2-server-php/tree/introspection
Test with :
https://github.com/bdegoy/oauth2-server-php-introspection-test
Discuss :
https://github.com/bshaffer/oauth2-server-php/pull/964