Uploaded image for project: 'OpenAM'
  1. OpenAM
  2. OPENAM-16566

OAuth2 Access token obtained from refresh token is certificate-bound regardless of "Certificate-Bound Access Tokens" configuration with POST authentication

    XMLWordPrintable

    Details

    • Bug
    • Status: Closed
    • Major
    • Resolution: Fixed
    • 6.5.2.2
    • 6.5.3
    • oauth2
    • Rank:
      1|i010jf:i
    • AM Sustaining Sprint 77
    • 5
    • Yes
    • No
    • Yes and I used the same an in the description, Yes but I used my own steps. (If so, please add them in a new comment)

      Description

      Bug description

      Refreshed OAuth2 Access token (obtained with refresh token) is certificate-bound, regardless of "Certificate-Bound Access Tokens" configuration in Oauth2 client configuration. See: https://backstage.forgerock.com/docs/am/6.5/oauth2-guide/#PoP-Cert

      How to reproduce the issue

      1. Configure a new AM instance and container for HTTPS (accept client certs)
      2. Configure an Oauth2 Provider as described here: https://backstage.forgerock.com/docs/am/6.5/oauth2-guide/#common-task-oauth2-authz including optional step 4. "Select Issue Refresh Tokens if you want the provider to supply a refresh token when returning an access token". No other optional steps required.
      3. Register an OAuth2 client profile: https://backstage.forgerock.com/docs/am/6.5/oauth2-guide/#register-oauth2-client and set POST as authentication method, add "refresh token" to grant types
      4. Do not enable certificate bound access tokens in the OAuth2 client configuration. Do not enable "Support TLS Certificate-Bound Access Tokens" in the OAuth2 Provider configuration.
      5. Refresh an OAuth2 access token. Below is an example of my tests, note that you must send a client certificate with each request (either as trusted header, or a real certificate. See: https://backstage.forgerock.com/docs/am/6.5/oauth2-guide/#provide-mtls-certs

       
      Authenticate: 

      curl --location --request POST 'https://openam.example.com:8443/openam/json/realms/root/authenticate' \
       --header 'Content-Type: application/json' \
       --header 'X-OpenAM-Username: demo' \
       --header 'X-OpenAM-Password: changeit' \
       --header 'Accept-API-Version: resource=2.0, protocol=1.0' \
       {
       "tokenId": "54h9wu7LOztMAylyHNXxNKcB_rI.AAJTSQACMDEAAlNLABxiUGxQcEVEZ0pMdEVlZTZndE1EdlpVenlqQkU9AAR0eXBlAANDVFMAAlMxAAA.",
       "successUrl": "/openam/console",
       "realm": "/"
       }

       
      Authorize (AUTHORIZATION_CODE flow):

      curl --location --request POST 'https://openam.example.com:8443/openam/oauth2/realms/root/authorize' \
       --header 'Content-Type: application/x-www-form-urlencoded' \
       --header 'Cookie: amlbcookie=01; iPlanetDirectoryPro=54h9wu7LOztMAylyHNXxNKcB_rI.AAJTSQACMDEAAlNLABxiUGxQcEVEZ0pMdEVlZTZndE1EdlpVenlqQkU9AAR0eXBlAANDVFMAAlMxAAA.' \
       --data-urlencode 'scope=write' \
       --data-urlencode 'response_type=code' \
       --data-urlencode 'client_id=myClient' \
       --data-urlencode 'csrf=54h9wu7LOztMAylyHNXxNKcB_rI.AAJTSQACMDEAAlNLABxiUGxQcEVEZ0pMdEVlZTZndE1EdlpVenlqQkU9AAR0eXBlAANDVFMAAlMxAAA.' \
       --data-urlencode 'redirect_uri=https://www.example.com:443/callback' \
       --data-urlencode 'state=abc123' \
       --data-urlencode 'decision=allow'
        
       <Get code from response header>

       
      Get access token and refresh token:

      curl --location --request POST 'https://openam.example.com:8443/openam/oauth2/realms/root/access_token' \
       --header 'Content-Type: application/x-www-form-urlencoded' \
       --header 'Cookie: amlbcookie=01; iPlanetDirectoryPro=54h9wu7LOztMAylyHNXxNKcB_rI.AAJTSQACMDEAAlNLABxiUGxQcEVEZ0pMdEVlZTZndE1EdlpVenlqQkU9AAR0eXBlAANDVFMAAlMxAAA.' \
       --data-urlencode 'grant_type=authorization_code' \
       --data-urlencode 'code=bT4RZhjFgddPzhUuiscpej-0hPc' \
       --data-urlencode 'client_id=myClient' \
       --data-urlencode 'client_secret=forgerock' \
       --data-urlencode 'redirect_uri=https://www.example.com:443/callback'
        
       {
       "access_token": "ondDLk188pu3U6PZWwRMc3Rh4g4",
       "refresh_token": "eP6crGmVg1E2Rs34cj4Y7d2mWmM",
       "scope": "write",
       "token_type": "Bearer",
       "expires_in": 9999
       }

       
      Refresh access token:

      curl --location --request POST 'https://openam.example.com:8443/openam/oauth2/realms/root/access_token' \
       --header 'Content-Type: application/x-www-form-urlencoded' \
       --header 'Cookie: amlbcookie=01; iPlanetDirectoryPro=54h9wu7LOztMAylyHNXxNKcB_rI.AAJTSQACMDEAAlNLABxiUGxQcEVEZ0pMdEVlZTZndE1EdlpVenlqQkU9AAR0eXBlAANDVFMAAlMxAAA.' \
       --data-urlencode 'client_id=myClient' \
       --data-urlencode 'grant_type=refresh_token' \
       --data-urlencode 'scope=write' \
       --data-urlencode 'refresh_token=eP6crGmVg1E2Rs34cj4Y7d2mWmM' \
       --data-urlencode 'client_secret=forgerock'
        
       {
       "access_token": "b9XB_AkkaWfE_ifKmlMxMf6XoeE",
       "scope": "write",
       "token_type": "Bearer",
       "expires_in": 9999
       }

       
      Introspect access token:

      curl --location --request POST 'https://openam.example.com:8443/openam/oauth2/realms/root/introspect' \
       --header 'Authorization: Basic bXlDbGllbnQ6Zm9yZ2Vyb2Nr' \
       --header 'Content-Type: application/x-www-form-urlencoded' \
       --header 'Cookie: amlbcookie=01; iPlanetDirectoryPro=54h9wu7LOztMAylyHNXxNKcB_rI.AAJTSQACMDEAAlNLABxiUGxQcEVEZ0pMdEVlZTZndE1EdlpVenlqQkU9AAR0eXBlAANDVFMAAlMxAAA.' \
       --data-urlencode 'token=b9XB_AkkaWfE_ifKmlMxMf6XoeE'
        
       {
       "active": true,
       "scope": "write",
       "client_id": "myClient",
       "user_id": "demo",
       "token_type": "Bearer",
       "exp": 1595968594,
       "sub": "demo",
       "iss": "https://openam.example.com:8443/openam/oauth2",
       "auth_level": 0,
       "cnf": {
       "x5t#S256": "BZVZVZPKYrweu0xwyP9hbr-GVcK4_4gkUYSBSawFhUc"
       },
       "auditTrackingId": "95f20ee2-fc81-42e3-90c0-c2f2036b8214-15857"
       }
        

      Note that here we have a cnf value, showing that the access token is bound to the client cert:

      "cnf":
      { "x5t#S256": "BZVZVZPKYrweu0xwyP9hbr-GVcK4_4gkUYSBSawFhUc"  
       
      
      

       

      Use the refreshed access token on /userinfo endpoint with different client cert:   

       

      curl --location --request GET 'https://openam.example.com:8443/openam/oauth2/realms/root/userinfo' \ --header 'Authorization: Bearer b9XB_AkkaWfE_ifKmlMxMf6XoeE' \ --header 'Cookie: amlbcookie=01; iPlanetDirectoryPro=54h9wu7LOztMAylyHNXxNKcB_rI.*AAJTSQACMDEAAlNLABxiUGxQcEVEZ0pMdEVlZTZndE1EdlpVenlqQkU9AAR0eXBlAANDVFMAAlMxAAA.*'
      { "error_description": "The access token provided is expired, revoked, malformed, or invalid for other reasons.", "error": "invalid_token" }  
      
      

       

       

      OAuth2Provider debug log error:

       

      07/28/2020 05:54:45:632 PM UTC: Thread[https-jsse-nio-8443-exec-8,5,main]: TransactionId[c99f7bad-a8cf-41de-864a-a85602f12cbc-5783981] Access token has an unsatisfied proof-of-possession constraint: confirmationMethod = x5t#S256, confirmationKey = "BZVZVZPKYrweu0xwyP9hbr-GVcK4_4gkUYSBSawFhUc"  
      
      

       

      Expected behaviour

      With no certificate proof of possession configured in either OAuth2 Provider or client configuration, access tokens should not be certificate bound (no cnf key).

      /userinfo endpoint should not enforce certificate proof of possession. 

       

      Observations

      **This only appears to access tokens obtained via refresh_token grant type, although I have not tested all the possible grant_types

        Attachments

          Issue Links

            Activity

              People

              lawrence.yarham Lawrence Yarham
              john.noble John Noble
              Votes:
              3 Vote for this issue
              Watchers:
              11 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: