Class FragmentFilter
- All Implemented Interfaces:
Filter
The FragmentFilter supports URIs that contain fragments, keeping track of the fragment part when a request
triggers a login redirect. The browser does not include the fragment (also referred to as an anchor) in the
request that is sent to IG which means it can't be included when redirecting as part of a login process and is lost
when the login process completes.
This filter does not handle multiple fragment capture in parallel. Then, if a fragment has been captured and IG performs another redirection of the user agent, the fragment capture process will not be triggered, keeping only the first fragment for replay.
How it works
The general flow with this filter in place is as follows:
- When an incoming request is received by this filter, it adds an
AuthRedirectContextfor the downstream filters to be able to mark response as redirected when needed. - When the response message is intercepted, and if it has been marked as a login redirect (thanks to the
AuthRedirectContext), this filter:- captures both the Location header value (the original login URI) and the original request URI (the target of the original request)
- creates a nonce to avoid open redirect, XSS and replay vulnerabilities
- returns a new response object containing an auto-submit HTML form, all the cookies set on the original response (otherwise they would be lost), and another one containing the nonce
- the original login URI
- the original request URI
- the nonce value
- a JavaScript script used to capture the fragment on the client side and send them back to the filter
- The User-Agent either runs the JavaScript or displays the form's submit button for the user to click on. Effectively, this POST back to the fragment endpoint URI a form request with both the original login URI, the original request URI, the captured fragments, and the nonce (both in the form and in a cookie).
- POSTed parameters are received by the fragment filter's endpoint which will check if the nonce value in the form and in the cookie matches. If true, it stores the fragments in a dedicated cookie that we expect to receive back when the user-agent performs the last redirect on the original request URI.
- This filter then returns a redirect response to the original/captured login URI, in order to resume the authentication flow.
- The required authentication flow takes place between the user-agent and the IDP before eventually completing with a final redirect to the original request URI.
- If there is a fragment cookie in the request, and if the request URI matches the original request URI (read from the cookie), then this filter intercepts the flow and redirects the UA a final time on an URI composed of the original request URI and the captured fragments. The fragment cookie is expired during that step.
- On following this final redirect, the UA can use the fragments.
Configuration
The filter configuration is as follows:
{
"type": "FragmentFilter",
"config": {
"fragmentCaptureEndpoint" : URI Path [REQUIRED - the IG endpoint URI used to capture any
fragment that may have been part of the
original request URI. This should match
the condition that triggers the route this
filter is used in to ensure the form data
holding the fragment is captured correctly.]
"noJavaScriptMessage: : stringExpression [OPTIONAL - the message to show when the user-agent does
not support JavaScript. JavaScript is used to
capture the fragment by the generated HTML
page sent to the browser. Defaults to
{@literal NO_JAVASCRIPT_MESSAGE}
"cookie": {
"name" : Name of cookie containing the IG fragment details. Defaults to
{@value Heaplet#DEFAULT_FRAGMENT_COOKIE_NAME }.
"domain" : Domain that cookie is applicable to. If unset, defaults to domain of IG host.
"path" : Path protected by this authentication. Defaults to
{@value Heaplet#DEFAULT_FRAGMENT_COOKIE_PATH }.
"secure" : Determines if the cookie should be set to be secure. Defaults to {@code false}.
"httpOnly" : Determines if the cookie should be set to be httpOnly. Defaults to {@code true}.
"sameSite" : SameSite cookie configuration. Default is {@code null}.
"maxAge" : Max-Age cookie configuration. Default is {@literal 1 hour}
}
"nonceCookie": {
"name" : Name of cookie containing the anti-open-redirect nonce. Defaults to
{@value Heaplet#DEFAULT_NONCE_COOKIE_NAME}.
"secure" : Determines if the cookie should be set to be secure. Defaults to {@code false}.
}
}
}
Security considerations
The auto-submitted form could be forged or tampered with by an attacker to perform an open redirect attack.
While we acknowledge this filter can be tampered with in a man-in-the-middle attack, we assume it'll be mitigated in real life by using a secure channel (such as TLS) for the HTTP exchanges.
Among the possible solutions to secure the content of the auto-submitted form, we discarded:
- Signing the form content as it would have added complexity in the filter configuration with a set of keys to manage.
- Using the session instead of the form as it would require specific configuration for multi-instance deployments such as session stickiness or self-encoded sessions.
- a strict "Content Security Policy", to prevent injection of malicious JavaScript
- a nonce stored both in the self-submitted form and in a Cookie secured with
HttpOnlyandSameSite=Strict. So the filter, by comparing both values, will assert that the form has not been forged by an attacker.
This strategy works well thanks to the "Same origin policy" enforced by browsers. Indeed, attackers from another domain won't be able:
- To get the form content using JavaScript, thanks to the "Same Origin Policy"
- To read/write the Cookie value, thanks to the way Browsers secure cookies
- To send the Cookie, thanks to the SameSite=Strict attribute cookie HttpOnly attribute which prevents the cookie from being accessed by JavaScript (thanks to the cookie HttpOnly and SameSite attributes) when fetching the form using JavaScript. Making them unable to forge the form with a valid nonce value nor to reuse a captured form as the nonce will be different for each form.
Limitations
- If attackers are on the same domain, the Same Origin Policy does not apply, but she would have no usage of an open redirect vulnerability
- if a CORS policy such as
Access-Control-Allow-Origin=*has been set up, then the protection is weakened and the attacker could fetch the form, update it and trick the user to submit it. However, cookies won't be sent alongside the request thanks toSameSite=strictattribute.
- See Also:
-
- URI Fragment // @Checkstyle:on LineLength
- Fragment RFC
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic classCreates and initialises aFragmentFilterin a heap environment. -
Method Summary
-
Method Details
-
filter
public Promise<Response,NeverThrowsException> filter(Context context, Request request, Handler next) Description copied from interface:FilterFilters the request and/or response of an exchange. To pass the request to the next filter or handler in the chain, the filter callsnext.handle(context, request).This method may elect not to pass the request to the next filter or handler, and instead handle the request itself. It can achieve this by merely avoiding a call to
next.handle(context, request)and creating its own response object. The filter is also at liberty to replace a response with another of its own by intercepting the response returned by the next handler.
-