Uploaded image for project: 'Apache NiFi'
  1. Apache NiFi
  2. NIFI-8406

Oidc Identity Provider should support assertions as client credentials for authenticating against the token endpoint

    XMLWordPrintableJSON

Details

    Description

      The current oidc client authentication methods (client_secret_post, client_secret_basic) require client credentials (client_secret) to be stored as plain text on the client's filesystem, which could also be inadvertently checked into source control system.

      Due to these and other security considerations, we should be able to use assertions as client credentials for authenticating against the token endpoint. 

      While using assertions an oidc client will include client_assertion and client_assertion_type parameters instead of passing the client_secret for authentication.

      Details on the OAuth 2.0 specifications for client authentication using jwt and assertions can be found under

      1. RFC 7523, Section 2.2 (Using JWTs for Client Authentication) 
      2. RFC 7521 (Using Assertions for Client Authentication).

      Summary of the changes needed for assertion based client authentication.

      • Generate a pair of private and public key which can be made available to Nifi via a keystore on the filesystem. 
      • Make the public key or cert available to the Authorization Server.     Alternatively to allow key rotations, we could configure a JWKS URL within Nifi to allow the authorization server download the public key using the keyId (kid).
      • While building the token request against the token endpoint, need StandardOidcIdentityProvider to provide a way to build a private_key_jwt based client authentication in addition to the existing client_secret_basic, client_secret_post. 
        if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.equals(clientAuthenticationMethod)){    
             clientAuthentication = new PrivateKeyJWT(clientId, tokenEndpoint, jwsAlgorithm, privateKey, keyId, null);
        }else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientAuthenticationMethod)) { 
             clientAuthentication = new ClientSecretPost(clientId, clientSecret);
        } else { 
             clientAuthentication = new ClientSecretBasic(clientId, clientSecret);
        }
      • Encode and sign the JWT token with the private key, conforming to RFC 7523, section 2.2 and pass the signed token within the client_assertion request parameter along with the client_assertion_type. 
        POST https://<auth-host>:<port>/.../token HTTP/1.1
        Content-Type: application/x-www-form-urlencoded
        client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
        client_assertion=eyJraWQiOiJkMlY2LS1OUG....hTMfA&
        grant_type=authorization_code&
        code=9ba8d85b-...-c7a6fb6dae55
        
      • The Authorization Server will use the client's public key or cert to verify the signature of the JWT and issues the token

       

      Nifi Token Exchange Process

      When authentication within Nifi is enabled via OpenId Connect, a user is re-directed to the configured authorization endpoint (authorization server) via Nifi's oidc/request endpoint. The user upon successfully authenticating with the authorization server is directed back to Nifi's (oidc/callback endpoint) with a temporary authorization code. This authorization code is then exchanged for an ID Token by the OidcService via the Oidc Identity Provider (StandardOidcIdentityProvider - exchangeAuthorizationCode(authorizationGrant)). The identity provider requests this exchange by authenticating itself against the token endpoint of the authorization server and presenting the authorization grant. The authorization server authenticates the client, validates the authorization grant and upon successful validation it issues an access token to Nifi.

      Currently, the exchangeAuthorizationCode(...) on StandardOidcIdentityProvider provides two methods of Client Authentication (client_secret_basic, client_secret_post) which are both based on the Authorization Server issuing a set of client credentials (client_secret) which are presented to the authorization server as client credentials during token exchange. 

      public String exchangeAuthorizationCode(final AuthorizationGrant authorizationGrant) throws IOException { 
      ...
      
      // 1 - Build the client authentication using the clientId and clientSecret obtained during the registration of the client with the authorization server
      if (oidcProviderMetadata.getTokenEndpointAuthMethods().contains(ClientAuthenticationMethod.CLIENT_SECRET_POST)) { 
      clientAuthentication = new ClientSecretPost(clientId, clientSecret); 
      } else {
       clientAuthentication = new ClientSecretBasic(clientId, clientSecret); 
      }
      
      // 2 - Build the token endpoint request with the above client authentication 
      TokenRequest request = new TokenRequest(tokenEndpoint, clientAuthentication, authorizationGrant); 
      HTTPRequest tokenHttpRequest = request.toHTTPRequest();
      ...
      
      // 3 - Submit the token request and Get the token response TokenResponse response = OIDCTokenResponseParser.parse(tokenHttpRequest.send());
      
      // 4 - Upon Successful Response get the ID Token and the User Identity 
      if (response.indicatesSuccess()) { 
      final OIDCTokenResponse oidcTokenResponse = (OIDCTokenResponse) response; 
      final OIDCTokens oidcTokens = oidcTokenResponse.getOIDCTokens();  
      
      // validate the access token and parse the claims  
      final IDTokenClaimsSet claimsSet = tokenValidator.validate(oidcJwt, null);  
      
      // attempt to extract the configured claim to access the user's identity; default is 'email' 
      String identity = claimsSet.getStringClaim(properties.getOidcClaimIdentifyingUser()); ... 
      }
      
      // 5 - If User Identity could not extracted out of the IDToken, lookup the identity against the UserInfo endpoint 
      if (StringUtils.isBlank(identity)) { 
      ... 
      final BearerAccessToken bearerAccessToken = oidcTokens.getBearerAccessToken(); 
      ... 
      // 7 - Invoke the UserInfo endpoint to lookup user identity against the UserInfo endpoint 
      identity = lookupIdentityInUserInfoModified(bearerAccessToken);  ...
      } 
      ... 
      
      // 8 - Convert into a Nifi jwt for retrieval later 
      final LoginAuthenticationToken loginToken = new LoginAuthenticationToken(identity, identity, expiresIn, claimsSet.getIssuer().getValue()); 
      return jwtService.generateSignedToken(loginToken);
      }

       

      Attachments

        Activity

          People

            Unassigned Unassigned
            vjammi Vijay Jammi
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: