Pierre Villard created NIFI-14380:
-------------------------------------

             Summary: OAuth 2.0 Controller Service to support JWT Bearer Flow
                 Key: NIFI-14380
                 URL: https://issues.apache.org/jira/browse/NIFI-14380
             Project: Apache NiFi
          Issue Type: New Feature
          Components: Extensions
            Reporter: Pierre Villard
            Assignee: Pierre Villard


The goal of this JIRA is to have a new implementation of the 
OAuth2AccessTokenProvider in order to support the JWT Bearer Flow.

Looking at different services supporting this option, it looks like that each 
implementation have some nuances that will require some handling in order to 
provide a generic solution that works in all of those situations.

Services that I took into consideration for the implementation:
h2. Google Identity

[https://developers.google.com/identity/protocols/oauth2/service-account#httprest]

The JWT header should include:
 * alg = RS256
 * typ = JWT
 * kid (optional for the key ID)

Required JWT claims:
 * iss (issuer)
 * scope
 * aud (audience)
 * exp (expiration)
 * iat (issued at)

Additional optional JWT claim: sub

The access token request should be application/x-www-form-urlencoded and have 
the following parameters:
 * grant_type = urn:ietf:params:oauth:grant-type:jwt-bearer
 * assertion = signed JWT

h2. Salesforce

[https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_oauth_jwt_flow.htm&type=5]

The JWT header should include:
 * alg = RS256

Required JWT claims:
 * iss (issuer)
 * aud (audience)
 * sub (subject)
 * exp (expiration)

The access token request should be application/x-www-form-urlencoded and have 
the following parameters:
 * grant_type = urn:ietf:params:oauth:grant-type:jwt-bearer
 * assertion = signed JWT
 * format (optional to specify the format of the response, JSON is default)

Note that the access token returned by Salesforce does NOT include an 
expiration information.
h2. Microsoft

[https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#second-case-access-token-request-with-a-certificate]

The JWT header should include:
 * alg = PS256
 * typ = JWT
 * x5t = thumbprint of the certificate

Required JWT claims:
 * iss (issuer)
 * aud (audience)
 * sub (subject)
 * exp (expiration)
 * jti (JWT ID)
 * nbf (not before)
 * iat (issued at)

The access token request should be application/x-www-form-urlencoded and have 
the following parameters:
 * grant_type = client_credentials
 * tenant
 * client_id
 * scope
 * client_assertion_type = 
urn:ietf:params:oauth:client-assertion-type:jwt-bearer
 * client_assertion = signed JWT
 * format (optional to specify the format of the response, JSON is default)

h2. Box

[https://developer.box.com/guides/authentication/jwt/without-sdk/#3-create-jwt-assertion]

The JWT header should include:
 * alg = RS256, RS384, or RS512
 * typ = JWT
 * kid = key ID

Required JWT claims:
 * iss (issuer)
 * aud (audience)
 * sub (subject)
 * exp (expiration)
 * jti (JWT ID)
 * nbf (not before)
 * iat (issued at)
 * box_sub_type - custom claim for Box

The access token request should be application/x-www-form-urlencoded and have 
the following parameters:
 * grant_type = client_credentials
 * client_id
 * client_secret
 * assertion = signed JWT

—

For additional information:

[https://cloudentity.com/developers/basics/oauth-grant-types/using-jwt-profile-for-authorization-flows/]

[https://cloudentity.com/developers/api/authorization_apis/oauth2/#tag/oauth2/operation/token]

—
h2. Requirements

Given the above examples, here are some requirements for the implementation:
 * there are many potential fields in the JWT header and the initial 
implementation will not support all fields. Focus will be on supporting alg, 
typ, kid and x5t. The latest requires a certificate to be provided in addition 
to the private key for signing the JWT. Given that x5t does not seem to be 
commonly required, the implementation will require a KeyPairService to be 
specified to provide the private key and if x5t is needed in the JWT header, 
then a SSL Context Service will also be required to provide the certificate.
 * some custom claims may be needed so support for dynamic properties will be 
required so that a user can provide custom claims in addition to the common 
ones that will be exposed in the configuration via dedicated properties. 
Dynamic properties where the key will be prefixed by CLAIM. will be used for 
custom claims.
 * the request made against the token endpoint may have very different form 
parameters so support for custom params is also required. Dynamic properties 
where the key will be prefixed by FORM. will be used for custom form 
parameters. Besides we have seen that some sensitive values may be set there 
(client_secret) so support for sensitive dynamic properties is required. 
Finally, the parameter for the signed JWT is not always named "assertion" so 
specific handling is required for this.
 * the returned access token may not have an expiration date so we need special 
handling for re-acquiring a new access token. The following strategy is used:
 ** expose an expiration time property for the JWT that defaults to 1 hour
 ** if the retrieved access token has an expiration date, use this + refresh 
window to acquire a new access token
 ** if the retrieved access token does not have an expiration date, use the 
expiration time of the JWT + refresh window to acquire a new access token
 * Signing algorithms supported should also support more robust options even if 
above listed services are not always expecting the most secure ones. The 
following list of signing algorithms should be supported
 ** RS256, RS384, RS512
 ** PS256, PS384, PS512
 ** ES256, ES384, ES512
 ** Ed25519
 * Special validation will be required to make sure that the algorithm of the 
private key provided by the user is compatible with the signing algorithm. 
RS/PS should be with RSA Private Key, ES should be with EC Private Key, etc.

h2. Configuration

Following configuration is considered for the implementation:
 * Token Endpoint - URL to request to get the access token
 * Web Client Service to perform the request
 * Private Key Service to provide the private key for signing the JWT
 * Signing Algorithm with allowable values mentioned before
 * Issuer - optional
 * Subject - optional
 * Audience - optional - space separated values if multiples should be specified
 * Scope - optional
 * NBF - true/false to include the not before time that will be set to same 
value as issue time
 * JTI - optional - recommendation to use expression language ${UUID()} is set
 * Header Type - true/false to include or not typ=JWT in the header
 * Header X5T - true/false to include or not the x5t field with the thumbprint 
of the certificate. If set to true, then
 ** required SSL Controller Service to provide the certificate to use
 * KID - optional
 * Grant Type - value to use for the grant_type form parameter, defaults to 
urn:ietf:params:oauth:grant-type:jwt-bearer
 * Assertion Field - name of form parameter that should contain the signed JWT 
(defaults to assertion but can be overridden to account for the Microsoft case)
 * Refresh window - defaults to 5 minutes
 * JWT validity - defaults to 1 hour

In addition, support for sensitive dynamic properties with
 * CLAIM.<key> = <value> - to set custom claims
 * FORM.<key> = <value> - to set custom form parameters in the request

h2. Conclusion

With the proposed configuration options above, here is how the controller 
service would be configured for the different services
h3. Google Identity
 * Token Endpoint - [https://oauth2.googleapis.com/token]
 * Signing Algorithm - RS256
 * Issuer - The email address of the service account
 * Subject - optional - The email address of the user for which the application 
is requesting delegated access
 * Audience - [https://oauth2.googleapis.com/token]
 * Scope - A space-delimited list of the permissions that the application 
requests
 * NBF - false
 * JTI - not set
 * Header Type - true
 * Header X5T - false
 * KID - key ID of the service account key
 * Grant Type - urn:ietf:params:oauth:grant-type:jwt-bearer
 * Assertion Field - assertion

h3. Salesforce
 * Token Endpoint - [https://.../services/oauth2/token]
 * Signing Algorithm - RS256
 * Issuer - The issuer must contain the OAuth client_id or the connected app 
for which you registered the certificate
 * Subject - optional - If you’re implementing this flow for an Experience 
Cloud site, the subject must contain the user’s username.
 * Audience - The audience identifies the authorization server as an intended 
audience.
 * Scope - not set
 * NBF - false
 * JTI - not set
 * Header Type - false
 * Header X5T - false
 * KID - not set
 * Grant Type - urn:ietf:params:oauth:grant-type:jwt-bearer
 * Assertion Field - assertion

h3. Box
 * Token Endpoint - https://api.box.com/oauth2/token
 * Signing Algorithm - RS256, RS384, or RS512
 * Issuer - The Box Application's OAuth client ID
 * Subject - The Box Enterprise ID if this app is to act on behalf of the 
Service Account of that application, or the User ID if this app wants to act on 
behalf of another user.
 * Audience - https://api.box.com/oauth2/token
 * Scope - not set
 * NBF - true
 * JTI - ${UUID()}
 * Header Type - true
 * Header X5T - false
 * KID - The ID of the public key used to sign the JWT. Not required, though 
essential when multiple key pairs are defined for an application.
 * Grant Type - urn:ietf:params:oauth:grant-type:jwt-bearer
 * Assertion Field - assertion
 * CLAIM.box_sub_type - enterprise or user depending on the type of token being 
requested in the sub claim
 * FORM.client_id - Client ID
 * FORM.client_secret - Client Secret

h3. Microsoft
 * Token Endpoint - https://.../<tenant>/oauth2/v2.0/token
 * Signing Algorithm - PS256
 * Issuer - Use the GUID application ID.
 * Subject - Use the same value as issuer.
 * Audience - https://login.microsoftonline.com/<tenantId>/oauth2/v2.0/token
 * Scope - not set
 * NBF - true
 * JTI - ${UUID()}
 * Header Type - true
 * Header X5T - true + SSL Controller Service to provide certificate
 * KID - not set
 * Grant Type - client_credentials
 * Assertion Field - client_assertion
 * FORM.client_id - The application ID that's assigned to your app.
 * FORM.tenant - The directory tenant the application plans to operate against, 
in GUID or domain-name format.
 * FORM.scope - The value passed for the scope parameter in this request should 
be the resource identifier (application ID URI) of the resource you want, 
suffixed with .default. All scopes included must be for a single resource. 
Including scopes for multiple resources will result in an error. For the 
Microsoft Graph example, the value is https://graph.microsoft.com/.default.
 * FORM.client_assertion_type - 
urn:ietf:params:oauth:client-assertion-type:jwt-bearer





--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to