Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology
code share

CORS implementation

Problem this snippet solves:

Pretty self-explanatory - we had to implement CORS (Cross-Origin-Resource-Sharing) where we had multiple domains, all of which had to be able to make AJAX calls to API's in our 'api.example.com' subdomain. Additionally, we had some partners who also need to be able to call our API's. In some cases, we had to pass cookies in the request.

In the past, various developers had created backend Java code to return the CORS response headers, but almost invariably they did an incomplete job - either returning an invalid value or not returning all the required headers or writing the code such that it wasn't portable across applications. Therefore, I decided to write some 'common' CORS handling code, which would have the benefit of doing 'proper' origin checking and would also immediately return the OPTIONS preflight response directly from F5, thus improving performance.

After much hacking around, here is what I came up with.

We used a class to define multiple top-level domains as 'allowed' origins - this would contain both your domains and also those of any partners whom you want to allow to make CORS requests to your site.

If you just have multiple subdomains on a single domain (e.g. www.example.com, api.example.com, code.example.com), you could simply use [HTTP::header Origin] ends_with ".example.com" - it's a little simpler.

I'm always returning the Access-Control-Allow-Credentials: true response header for 'valid' non-OPTIONS (e.g. GET/POST) CORS requests, even if it's not required (i.e. even if the withCredentials flag was not set in the request) - unfortunately, there is no way to know whether it is needed simply by looking at the request, so it's the only way to ensure client errors don't occur.

I'm passing the value of the Access-Control-Request-Method request header in the Access-Control-Allow-Methods response header (e.g. a single value of 'GET' or 'POST' or whatever) - in most implementations, you'll see people returning somethign like a string like Access-Control-Allow-Methods: GET, POST, PUT, but there's really no significant benefit to doing this - much simpler to only return what is passed. In either case, it will be cached by the browser because of the Access-Control-Max-Age response header.

Note that because you will be returning a specific Access-Control-Allow-Origin value, rather than '*', you should also return the Vary: Origin response header. This may have issues with browser caching or if you use a CDN like Akamai or Cloudflare - you should consult any CDN product documentation. There are multiple good sources for explaining the Vary header - Google is your friend.

If anyone has any comments, please add them, good or bad! I would love to know if someone finds this snippet useful...

Tested on Version:
Comments on this Snippet
Comment made 09-Sep-2016 by gis-att 1

Looks great. Reading some of the KB articles, it seems to indicate that I need the ASM module to use CORS headers. Do you know if this is true? (I have a pending ticket, but, unfortunately, this question is actively holding me up.

EDIT: ASM module is not required. This can be done via iRule. F5 support pointed me specifically to this page.

Comment made 12-Apr-2017 by jrojaso89 0


I have solved a issue applying this irule.

Thank you

Comment made 12-Apr-2017 by Cyril 115

Very useful code, and you can add that it solves an issue with the cookie persistence : when a browser sends a pre-flight request, by definition in RFC2616 it doesn't send any cookie with the OPTIONS method. So when the bigip receives the OPTIONS request, without cookie, it considers it as a new request, round robin the pool member, and in the http_reply the bigip includes a set_cookie ... The browser receives the set_cookie, thus drops the one it already had ... And voilà you lost your session :(

So with this iRule, the bigip doesn't send the pre-flight request to the pool member, and doesn't send a set_cookie back => your browser will keep the session cookie

Maybe F5 should consider a feature to not reply with a set_cookie to an OPTIONS request ?

Comment made 12-Apr-2017 by Rory Hewitt F5 107

@Cyril, your comment about new request pooling messing with cookies hadn't occurred to me - very good point!

Comment made 12-Apr-2017 by Cyril 115

I didn't challenge the F5 support yet to know if this whole "disable set-cookie when replying to an OPTIONS request" is something that can be done out of the box (and what firmware ?) or if using an iRule is mandatory ...

Comment made 08-Jan-2018 by DrLemongelo 11

After hours of struggling with syntax errors, support helped identify a correction I'll pass along.

1) I wasn't able to define the class inside of the iRule and had to create a Data Group object (type: string) that contained the allowed origins as String with a blank value.

If the though of using a GUI brings a tear to your eye, here's the CLI:

ltm data-group internal DG-CORS-ALLOWED-ORIGINS {
    records {
        .authorizeddomain.com { }
        localhost:3456 { }
    type string

2) The syntax in OP's iRule required the addition of more brackets.

This lines gives me an error: /Common/IRULE-CORS:10: error: [parse error: PARSE syntax 228 {syntax error in expression " class match [HTTP::header Origin] ends_with allowed_origins...": variable references require preceding $}][{ class match [HTTP::header Origin] ends_with allowed_origins }]

if { class match [HTTP::header Origin] ends_with DG-CORS-ALLOWED-ORIGINS }

Enclosed class in brackets and received no syntax errors:

if { [class match [HTTP::header Origin] ends_with DG-CORS-ALLOWED-ORIGINS] }

This was tested in LTM 11.6.0

Comment made 17-Apr-2018 by Rory Hewitt F5 107

Code snippet updated to include additional square brackets in the class match statement, as per the comment from @DrLemongelo.

Comment made 23-Apr-2018 by Kevin Dyer 0

I have not noticed any further comment about responses to set-cookie, so I thought I would respond. Section 7.1.5 Cross-Origin Request with Preflight of the latest CORS specification (https://www.w3.org/TR/cors/) states the client shall make the OPTIONS request with the "block cookie" flag set. For newer HTTP clients this means any response to an OPTIONS request with Origin header must ignore all set-cookie headers. But as others on this thread have noted, not all HTTP clients are up to date on handling set-cookie for a preflight response. Therefore, to err on the side of caution, I too strongly recommend handling the CORS preflight requests as close to the edge of the network as possible.

To piggyback on security settings I've also taken to add the header Content-Security-Policy: frame-ancestors 'self' '$cors_origin'; when a Origin header was found otherwise respond with the header Content-Security-Policy: frame-ancestors 'self';

Comment made 23-Apr-2018 by Rory Hewitt F5 107

@Kevin_Dyer note that the reference you cite for latest CORS specification is actually significantly out-of-date.

The responsibility for CORS was taken over by WHATWG, as part of the fetch spec (https://fetch.spec.whatwg.org/). The examples aren't great, but fetch now does allow (in theory) some enhancements, such as allowing an asterisk as the value for the Access-Control-Allow-Headers, Access-Control-Allow-Methods and Access-Control-Expose-Headers CORS response headers. I don't know whether all browsers have implemented support for these special values yet, however.

Comment made 3 months ago by Jeremy Desca 0

Hello, I would like to Apply this Irules, but i have an error when I create this.

01070151:3: Rule [/Common/IR_CROSS_DOMAIN_XXXXX.XXX] error: /Common/IR_CROSS_DOMAIN_XXXXX.XXX:2: error: [command is not valid in the current scope][class allowed_origins { ".XXXXX.XXX" }]

Anyone have a idea ?

Version : BIG-IP 11.5.3 Build 1.0.167 Hotfix HF1

Comment made 3 months ago by Cyril 115

You can put this class definition AFTER the "when HTTP_REQUEST {" and it should be fine

Comment made 1 month ago by krejzi_banan 0

I've got the same problem like Jeremy Desca any idea?

Comment made 1 month ago by Cyril 115

Well, didn't my previous comment solve the issue ?