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
- Resource Server (API Gateway)
- FAPI 2.0 Message Signing (Draft)
- FAPI 2.0 Miscellaneous Requirements
- References
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
Leave Comments
You must be logged in to post a comment.