FAPI 2.0 Compliance for OAuth Authorization Code flow

FAPI 2.0 Compliance for OAuth Authorization Code flow

In this post I discuss my take on the best options to get to FAPI 2.0 compliance in the OAuth Authorization Code flow and the objectives of each specification. This post is a consolidation of information derived from my experience and the sources in the references at the end of the article.


FAPI 2.0 Security Profile

OAuth authentication vulnerabilities arise partly because the OAuth specification is relatively vague and flexible by design. Although there are a handful of mandatory components required for the basic functionality of each grant type, the vast majority of the implementation is completely optional. This includes many configuration settings that are necessary for keeping users’ data secure.

One of the other key issues with OAuth is the general lack of built-in security features. The security relies almost entirely on developers using the right combination of configuration options and implementing their own additional security measures on top, such as robust input validation.

Depending on the grant type, highly sensitive data is also sent via the browser, which presents various opportunities for an attacker to intercept it. Vulnerabilities can arise in the client application’s implementation of OAuth as well as in the configuration of the OAuth service itself.

The FAPI 2.0 Security Profile is an API security profile based on the OAuth 2.0 Authorization Framework [RFC6749] and related specifications suitable for protecting APIs in high-value scenarios. While the security profile was initially developed with a focus on financial applications, it is designed to be universally applicable for protecting APIs exposing high-value and sensitive (personal and other) data, for example, in e-health and e-government applications.

Flawed CSRF protection

Although many components of the OAuth flows are optional, some of them are strongly recommended unless there’s an important reason not to use them. One such example is the state parameter.

The state parameter should ideally contain an un-guessable value, such as the hash of something tied to the user’s session when it first initiates the OAuth flow. This value is then passed back and forth between the client application and the OAuth service as a form of CSRF token for the client application. Therefore, if you notice that the authorization request does not send a state parameter, this is extremely interesting from an attacker’s perspective. It potentially means that they can initiate an OAuth flow themselves before tricking a user’s browser into completing it, similar to a traditional CSRF attack. This can have severe consequences depending on how OAuth is being used by the client application. The state parameter is required for FAPI 2.0 compliance.

Leaking authorization codes and access tokens

Perhaps the most infamous OAuth-based vulnerability is when the configuration of the OAuth service itself enables attackers to steal authorization codes or access tokens associated with other users’ accounts. By stealing a valid code or token, the attacker may be able to access the victim’s data.

Depending on the grant type, either a code or token is sent via the victim’s browser to the /callback endpoint specified in the redirect_uri parameter of the authorization request. If the OAuth service fails to validate this URI properly, an attacker may be able to construct a CSRF-like attack, tricking the victim’s browser into initiating an OAuth flow that will send the code or token to an attacker-controlled redirect_uri.

In the case of the authorization code flow, an attacker can potentially steal the victim’s code before it is used. They can then send this code to the client application’s legitimate /callback endpoint (the original redirect_uri) to get access to the user’s account. In this scenario, an attacker does not even need to know the client secret or the resulting access token. As long as the victim has a valid session with the OAuth service, the client application will simply complete the code/token exchange on the attacker’s behalf before logging them in to the victim’s account.

Note that using state or nonce protection does not necessarily prevent these attacks because an attacker can generate new values from their own browser.

PKCE (RFC 7636)

Proof Key for Code Exchange mitigates the risk of an authorization code injection attack. Prior to making a request to the authorization endpoint, the application generates a random string of 63 to 128 characters (code verifier) and the SHA-256 hash of that string (code challenge). The code challenge and hashing algorithm are provided as additional parameters to the /authorize endpoint. The code verifier is required to exchange the authorization code at the /token endpoint. Because the code verifier is only known to the application, the attacker cannot use a stolen authorization code returned by the /authorize endpoint to exchange the authorization code for tokens.

Supplementing PKCE is DPoP starting with the /authorize request. The Public Key in the DPoP header will be bound to the issued token(s) and will also be required for any request to the /token endpoint. Using DPoP somewhat simplifies the process as all requests to the Authorization Server can use the same mechanism. FAPI 2.0 requires PKCE, and DPoP is the simpler of the two options for Sender Constraints, so effectively both are required.

Adding Integrity to Authorization Requests

PAR (RFC 9126)

Pushed Authorization Requests also mitigates the risk of the authorization request being manipulated at the user agent (client browser). The application pushes the payload of an OAuth 2.0 authorization request to the authorization server via a direct request to the /par endpoint and provides the application with a request URI that is used as reference to the data in a subsequent call to the authorization endpoint. The PAR request can be made by a backend server which eliminates the request parameters from being exposed to the client application.

Adding Integrity to Authorization Responses

JARM (IETF Draft)

JWT Authorization Response Mode defines a new JWT-based mode to encode authorization responses. Clients are enabled to request the transmission of the authorization response parameters along with additional data in JWT format. This mechanism enhances the security of the standard authorization response since it adds support for signing and encryption, sender authentication, audience restriction as well as protection from replay, credential leakage, and mix-up attacks. JARM defines a new JWT-based mode to encode authorization responses parameters. All response parameters defined for a given response type are conveyed in a JWT along with additional fields used to further protect the transmission. Since there are different techniques to encode the JWT itself in the response to the client, namely query URI parameter, fragment component and form post, the draft defines a set of response mode values in accordance with Open ID Response Modes corresponding to these techniques. The two most common modes (query and form_post) are detailed below.

Response Mode query.jwt

The response mode “query.jwt” causes the authorization server to send the authorization response as HTTP redirect to the redirect URI of the client. The authorization server adds the parameter response containing the JWT as defined in section 4.1. to the query component of the redirect URI using the “application/x-www-form-urlencoded” format.

This is an example response (line breaks for display purposes only):

HTTP/1.1 302 Found
Location: https://client.example.com/cb?response=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmV4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsImV4cCI6MTMxMTI4MTk3MCwiY29kZSI6IlB5eUZhdXgybzdRMFlmWEJVMzJqaHcuNUZYU1FwdnI4YWt2OUNlUkRTZDBRQSIsInN0YXRlIjoiUzhOSjd1cWs1Zlk0RWpOdlBfR19GdHlKdTZwVXN2SDlqc1luaTlkTUFKdyJ9.HkdJ_TYgwBBj10C-aWuNUiA062Amq2b0_oyuc5P0aMTQphAqC2o9WbGSkpfuHVBowlb-zJ15tBvXDIABL_t83q6ajvjtq_pqsByiRK2dLVdUwKhW3P_9wjvI0K20gdoTNbNlP9Z41mhart4BqraIoI8e-L_EfAHfhCG_DDDv7Yg

Response Mode form_post.jwt

The response mode “form_post.jwt” uses the technique described in OAuth Form Post Response Mode to convey the JWT to the client. The response parameter containing the JWT is encoded as HTML form value that is auto-submitted in the User Agent, and thus is transmitted via the HTTP POST method to the Client, with the result parameters being encoded in the body using the “application/x-www-form-urlencoded” format.

This is an example response from the authorization server to the user agent (line breaks for display purposes only),

HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Cache-Control: no-cache, no-store
Pragma: no-cache

<html>
 <head><title>Submit This Form</title></head>
 <body onload="javascript:document.forms[0].submit()">
  <form method="post" action="https://client.example.com/cb">
    <input type="hidden" name="response"
     value="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2
      FjY291bnRzLmV4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsImV4cCI6MTM
      xMTI4MTk3MCwiYWNjZXNzX3Rva2VuIjoiMllvdG5GWkZFanIxekNzaWNNV3BBQSIs
      InN0YXRlIjoiUzhOSjd1cWs1Zlk0RWpOdlBfR19GdHlKdTZwVXN2SDlqc1luaTlkT
      UFKdyIsInRva2VuX3R5cGUiOiJiZWFyZXIiLCJleHBpcmVzX2luIjoiMzYwMCIsIn
      Njb3BlIjoiZXhhbXBsZSJ9.bgHLOu2dlDjtCnvTLK7hTN_JNwoZXEBnbXQx5vd9z1
      7v1HyzfMqz00Vi002T-SWf2JEs3IVSvAe1xWLIY0TeuaiegklJx_gvB59SQIhXX2i
      fzRmqPoDdmJGaWZ3tnRyFWNnEogJDqGFCo2RHtk8fXkE5IEiBD0g-tN0GS_XnxlE"/>
    </form>
   </body>
  </html>

Sender Constraints

FAPI 2.0 defines two methods for Sender Constraining authorization codes and tokens. The first is OAuth 2.0 Mutual TLS Client Authentication (MTLS RFC 8705) which would require issuing and managing certificates to client applications. The easier method to implement is DPoP which ensures that authorization codes and refresh tokens can only be used by the client that they were issued to. If the resource server supports DPoP, this protection ensures that an access token cannot be used by an attacker.

DPoP (RFC 9449)

Demonstrating Proof of Possession describes a mechanism for sender-constraining OAuth 2.0 tokens via a proof-of-possession mechanism on the application level. This mechanism allows for the detection of replay attacks with access and refresh tokens. Before making the /authorize endpoint call the application generates a private/public keypair and signs a JSON payload representing the /authorize request and public key with the generated private key. This “proof” is sent in the DPoP header to the authorization server. The authorization server validates the “proof” using the public key in the proof (which proves the client has the private key) and then binds the public key with the authorization grant as described in the next section.

GET /authorize?client_id=xxxx&request=. . . HTTP/1.1
Host: server.example.com
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik
VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR
nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE
QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiItQndDM0VTYzZhY2MybFRjIiwiaHRtIj
oiUE9TVCIsImh0dSI6Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwia
WF0IjoxNTYyMjYyNjE2fQ.2-GxA6T8lP4vfrg8v-FdWP0A0zdrj8igiMLvqRMUvwnQg
4PtFLbdLXiOSsX0x7NVY-FNyJK70nfbV37xRZT3Lg

If the authorization server supports DPoP, the public key included in the DPoP proof JWT will be bound to the authorization code and tokens generated.

The value of the "token_type" in the /token response will become "DPoP".

In contrast, if the authorization server doesn’t support DPoP, the DPoP HTTP header will be ignored and the public key won’t be bound to the access token. In this case, the value of the "token_type" in the /token response will become other value, typically "Bearer".

Binding the Access Token and Public Key

Binding a public key to an access token is achieved by remembering the hash value of the public key as one attribute of the access token. The hash value is “JWK SHA-256 Thumbprint” defined in RFC 7638.

If an access token is bound to a public key, an introspection request (RFC 7662) for the access token will receive a JSON that includes the hash value of the public key. To be concrete, the base64url expression of the JWK SHA-256 Thumbprint of the public key is included as the value of the jkt claim under the cnf claim.

"cnf": {
  "jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I"
}

When a Refresh Token is used by the application, the validation is similar to a resource server below with the added optional element of adding a nonce to the original access token which is required when using a refresh token. The nonce mitigates the risk of a replay attack.


Resource Server (API Gateway)

To ensure end-to-end security, the Resource Server has to support DPoP. When validating the Access Token, the Public Key Fingerprint will be available in either the JWT or with Introspection. This validation closes the circle.

The way of including a DPoP proof JWT in an API call is the same as for /token and /authorize requests. That is, the DPoP HTTP header is used.

On the other hand, there is a small difference when the Authorization header is used to include an access token.

In the traditional way defined in the section 2.1 of RFC 6750, an access token is passed as shown below.

Authorization: Bearer {Access-Token}

However, when the access token is bound to a public key, DPoP is used instead of Bearer like below.

Authorization: DPoP {Access-Token}

The following is an example of API call excerpted from the section 6 of the DPoP specification.

GET /protectedresource HTTP/1.1
Host: resource.example.com
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6IkJlQUxrYiJ9.eyJzdWI
 iOiJzb21lb25lQGV4YW1wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbX
 BsZS5jb20iLCJhdWQiOiJodHRwczovL3Jlc291cmNlLmV4YW1wbGUub3JnIiwibmJmI
 joxNTYyMjYyNjExLCJleHAiOjE1NjIyNjYyMTYsImNuZiI6eyJqa3QiOiIwWmNPQ09S
 Wk5ZeS1EV3BxcTMwalp5SkdIVE4wZDJIZ2xCVjN1aWd1QTRJIn19.vsFiVqHCyIkBYu
 50c69bmPJsj8qYlsXfuC6nZcLl8YYRNOhqMuRXu6oSZHe2dGZY0ODNaGg1cg-kVigzY
 hF1MQ
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik
 VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR
 nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE
 QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiJlMWozVl9iS2ljOC1MQUVCIiwiaHRtIj
 oiR0VUIiwiaHR1IjoiaHR0cHM6Ly9yZXNvdXJjZS5leGFtcGxlLm9yZy9wcm90ZWN0Z
 WRyZXNvdXJjZSIsImlhdCI6MTU2MjI2MjYxOH0.lNhmpAX1WwmpBvwhok4E74kWCiGB
 NdavjLAeevGy32H3dbF0Jbri69Nm2ukkwb-uyUI4AUg1JSskfWIyo4UCbQ

FAPI 2.0 Message Signing (Draft)

The Message Signing Profile is part of the FAPI 2.0 family of specifications with a focus on providing interoperable support for non-repudiation across OAuth 2.0 based requests and responses. Message signing combined with audit logging mitigates the risk of parties that could try to deny having sent a particular message, for example, a payment request. For this purpose, non-repudiation is needed.

In the context of this specification, non-repudiation refers to the assurance that the owner of a signature key pair that could generate an existing signature corresponding to certain data cannot convincingly deny having signed the data. This is usually achieved by providing application-level signatures that can be stored together with the payload and meaningful metadata of a request or response.

The following messages are affected by this specification:

  • REQ1: Pushed Authorization Requests
  • REQ2: Authorization Requests (Front-Channel)
  • RES3: Authorization Responses (Front-Channel)
  • RES4: Introspection Responses
  • REQ5: Resource Requests
  • RES6: Resource Responses

Signing Authorization Requests

To support non-repudiation for REQ1, Pushed Authorization Requests can be signed. Because FAPI2 uses JAR, REQ2 is achieved by default when the Pushed Authorization request is signed.

Signing Authorization Responses

To support non-repudiation for RES3, Authorization Responses can be signed. JARM fulfills this requirement.

Signing Introspection Responses

To support non-repudiation for RES4, Introspection Responses can be signed. JWT Response for OAuth Token Introspection is a draft specification to meet this requirement. This specification extends the token introspection endpoint with the capability to return responses as JWTs.

A resource server requests a JWT introspection response by sending an introspection request with an Accept HTTP header field set to “application/token-introspection+jwt”. POST /introspect HTTP/1.1Host: as.example.comAccept: application/token-introspection+jwtContent-Type: application/x-www-form-urlencoded token=2YotnFZFEjr1zCsicMWpAA

The introspection endpoint responds with a JWT, setting the Content-Type HTTP header field to “application/token-introspection+jwt” and the JWT typ (“type”) header parameter to “token-introspection+jwt”.

The JWT MAY include other claims, including those from the “JSON Web Token Claims” registry established by [RFC7519]. The JWT SHOULD NOT include the sub and exp claims, as an additional prevention against misuse of the JWT as an access token.

Note: Although the JWT format is widely used as an access token format, the JWT returned in the introspection response is not an alternative representation of the introspected access token and is not intended to be used as an access token.

POST /introspect HTTP/1.1
Host: as.example.com
Accept: application/token-introspection+jwt
Content-Type: application/x-www-form-urlencoded

token=2YotnFZFEjr1zCsicMWpAA

The introspection endpoint responds with a JWT, setting the Content-Type HTTP header field to “application/token-introspection+jwt” and the JWT typ (“type”) header parameter to “token-introspection+jwt”.

The JWT MAY include other claims, including those from the “JSON Web Token Claims” registry established by [RFC7519]. The JWT SHOULD NOT include the sub and exp claims, as an additional prevention against misuse of the JWT as an access token.

Note: Although the JWT format is widely used as an access token format, the JWT returned in the introspection response is not an alternative representation of the introspected access token and is not intended to be used as an access token.

HTTP Message Signing

To support non-repudiation for REQ5 and RES6, HTTP requests and responses to and from the Resource Server should be signed.

A future version of the profile expects to support HTTP Message Signing using the HTTP Message Signatures specification being developed by the IETF HTTP Working Group.

The specification being developed defines a mechanism for providing end-to-end integrity and authenticity for components of an HTTP message by use of a detached signature on HTTP messages. The mechanism allows applications to create digital signatures or message authentication codes (MACs) over only the components of the message that are meaningful and appropriate for the application. Strict rules ensure that the recipient can verify the signature even if the message has been transformed in many of the ways permitted by HTTP.

HTTP Signatures is a generic mechanism for signing any arbitrary HTTP request or response. The client assembles a specific list of request components (not the complete request) and signs them with a private key. This is added to the Signature header. The list of components and metadata is added to the Signature-Input header to communicate the order they were assembled.

GET /user/1
Host: resource-server.net
Authorization: Bearer XXXXX
Signature-Input: sig1=("@method" "@target-url" "authorization");created=1618937737;tag=fapi-2-request
Signature: sig1=:IoldnEshlrlisntocsifWeidng:

The resource server signs the response with list of components defined by the FAPI specification (Still in draft). Although the client can request the response be signed, the FAPI specification requires all responses be signed with a specific set of components. To request a signed response the client adds the Accept-Signature header specifying the input parameters and the key ID to use.

HTTP/1.1 200 OK
Content-Type: application/json
Content-Digest: sha-256-:aiKiensilshtkdllss/gjdislkg=:
Signature-Input: sig1=("@status" "content-digest");created=1618940931;tag=fapi-2-response
Signature: sig1=:THKss0ByEnskdilwnngi03skdn:

{
  "user_id": 1,
  "email": "user@example.com"
}

FAPI 2.0 Miscellaneous Requirements

The Authorization Server shall issue authorization codes with a maximum lifetime of 60 seconds.

Minimum length of Authorization Codes, Access and Refresh Tokens: 21 characters (Uppercase, Lowercase and Numbers – 128 bits of entropy)

TLS 1.2 permitted cipher suites
  • TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  • TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

References

FAPI 2.0

FAPI 2.0 Security Profile Implementers Guide

FAPI 2.0 Attacker Model

Illustrated DPoP (OAuth Access Token Security Enhancement)

About Post Author

Kevin Jeffery

Kevin has worked in the Services, Utilities and Finance Industries in IT Architecture, Administration and Process Design, and Software Development. With over 20 years of experience in Information Technology, Kevin currently works as a Cyber Security Consultant specializing in IAM deployment and operations automation.

Leave Comments