How to set CORS for SPA applications and embedded browsers
CORS, Cross-Origin Resource Sharing, is a HTTP-header mechanism to allow a browser to access a resource from another origin.
For applications without it's own backend, such as SPA applications and embedded browsers, CORS is an important feature as it allows such applications to use an external OIDC Provider for authentication and authorization.
PhenixID Authentication Services is always located behind an external load balancer / reverse proxy for http access. CORS is configured on the load balancer / reverse proxy. This document will guide the administrator of the load balancer / reverse proxy on how to allow CORS from external spa applications, OIDC RPs, to PhenixID Authentication Services acting as OIDC OP.
CORS requests from RP (SPA) to OP
Most SPAs acting as RPs need to perform three cross-origin requests from the client (web browser/javascript) directly to the OP:
- Discovery uri using HTTP GET
- jwks uri using HTTP GET
- Token endpoint using HTTP POST
Most SPAs also performs a so-called preflight request prior to the actual request. The purpose of a preflight request is the client asking the server "Will you allow me to perform this request?" The preflight request is a HTTP OPTIONS request with particular http headers on the request and the response.
Configuration requirements
The load balancer / reverse proxy must be configured to meet these requirements:
- Always set these response headers when requesting resources from SPAs
- Access-Control-Allow-Origin -> This must be set to the protocol://domain:port of the site requesting CORS (the SPA application)
- Access-Control-Allow-Methods -> This must be set to the allowed HTTP methods
- Access-Control-Allow-Headers -> This must be set to the allowed headers
- Always return status code 200 for HTTP OPTIONS requests
Configuration example
In this example, the SPA is a simple html page hosted on https://demo.phenixid.net.
The SPA is executing requests directly from the browser (javascript) to an OpenID Connect Provider hosted on https://integration.phenixid.se.
The requests made from the SPA to the OP resources are:
- Discovery. HTTP GET. https://integration.phenixid.se/oidc-conform/.well-known/openid-configuration
- JWKS. HTTP GET. https://integration.phenixid.se/oidc-conform/.well-known/openid-configuration/jwks
- Protected endpoint. HTTP POST. https://integration.phenixid.se:10443/api/authentication/cors_token_test?tenant=oidc-conform
The integration.phenixid.se reverse proxy is Apache HTTP. The configuration snippet below, added to httpd-ssl.conf, allow CORS from https://demo.phenixid.net.
(For other LB / proxy software, such as nginx, F5, Netscaler, please consult documentation to verify that the configuration is properly set.)
(If the application / web page where CORS is required is requesting other resources, please adjust your configuration accordingly)
#START Allow CORS <Location /oidc-conform/> RequestHeader unset Expect early Header always set Access-Control-Allow-Origin "https://demo.phenixid.net" Header always set Access-Control-Allow-Methods "GET, OPTIONS" Header always set Access-Control-Allow-Headers "Content-Type, Origin, Authorization, Accept, Accept-Encoding" RewriteEngine On RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ $1 [R=200,L] </Location> <Location /api/authentication/cors_token_test> RequestHeader unset Expect early Header always set Access-Control-Allow-Origin "https://demo.phenixid.net" Header always set Access-Control-Allow-Methods "POST, OPTIONS" Header always set Access-Control-Allow-Headers "Content-Type, Origin, Authorization, Accept, Accept-Encoding"
#Always respond with http status 200 for OPTIONS requests
RewriteEngine On RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ $1 [R=200,L] </Location> #END Allow CORS
Request-response examples
Discovery
Request
Preflight
OPTIONS https://integration.phenixid.se/oidc-conform/.well-known/openid-configuration HTTP/1.1
Accept: */*
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Origin: https://demo.phenixid.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Sec-Fetch-Dest: empty
Referer: https://demo.phenixid.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,sv;q=0.8
Resource
GET https://integration.phenixid.se/oidc-conform/.well-known/openid-configuration HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://demo.phenixid.net
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://demo.phenixid.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,sv;q=0.8
Response
Preflight
HTTP/1.1 200 OK
Date: Tue, 01 Dec 2020 07:17:28 GMT
Server: Apache/2.4.18 (Ubuntu)
Access-Control-Allow-Origin: https://demo.phenixid.net
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Origin, Authorization, Accept, Accept-Encoding
Content-Length: 584
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1
Resource
HTTP/1.1 200 OK
Date: Tue, 01 Dec 2020 07:17:29 GMT
Server: Apache/2.4.18 (Ubuntu)
Access-Control-Allow-Origin: https://demo.phenixid.net
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Origin, Authorization, Accept, Accept-Encoding
Content-Type: application/json
Content-Length: 999
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
{
....
}
JWKS
Request
Preflight
OPTIONS https://integration.phenixid.se/oidc-conform/.well-known/openid-configuration/jwks HTTP/1.1
Accept: */*
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Origin: https://demo.phenixid.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Sec-Fetch-Dest: empty
Referer: https://demo.phenixid.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,sv;q=0.8
Resource
GET https://integration.phenixid.se/oidc-conform/.well-known/openid-configuration/jwks HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://demo.phenixid.net
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://demo.phenixid.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,sv;q=0.8
Response
Preflight
HTTP/1.1 200 OK
Date: Tue, 01 Dec 2020 07:21:29 GMT
Server: Apache/2.4.18 (Ubuntu)
Access-Control-Allow-Origin: https://demo.phenixid.net
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Origin, Authorization, Accept, Accept-Encoding
Content-Length: 584
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1
Resource
HTTP/1.1 200 OK
Date: Tue, 01 Dec 2020 07:21:29 GMT
Server: Apache/2.4.18 (Ubuntu)
Access-Control-Allow-Origin: https://demo.phenixid.net
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Origin, Authorization, Accept, Accept-Encoding
Content-Type: application/json
Content-Length: 1633
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
{
....
}
Protected endpoint
Request
Preflight
OPTIONS https://integration.phenixid.se:10443/api/authentication/cors_token_test?tenant=oidc-conform HTTP/1.1
Accept: */*
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type
Origin: https://demo.phenixid.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Sec-Fetch-Dest: empty
Referer: https://demo.phenixid.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,sv;q=0.8
Resource
POST https://integration.phenixid.se:10443/api/authentication/cors_token_test?tenant=oidc-conform HTTP/1.1
Authorization: Bearer 123456789
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://demo.phenixid.net
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://demo.phenixid.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,sv;q=0.8
Response
Preflight
HTTP/1.1 200 OK
Date: Tue, 01 Dec 2020 07:29:07 GMT
Server: Apache/2.4.18 (Ubuntu)
Access-Control-Allow-Origin: https://demo.phenixid.net
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Origin, Authorization, Accept, Accept-Encoding
Content-Length: 584
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1
Resource
HTTP/1.1 200 OK
Date: Tue, 01 Dec 2020 07:29:07 GMT
Server: Apache/2.4.18 (Ubuntu)
Access-Control-Allow-Origin: https://demo.phenixid.net
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Origin, Authorization, Accept, Accept-Encoding
Content-Type: application/json
Content-Length: 652
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
{
....
}
Test and debug your environment
A test-cors SPA (which is a simple html page) can be found at https://demo.phenixid.net/cors-test.html
Simply change the protocol://domain:port values to point to your environment. Change the uri:s to suite your environment. Make sure to add https://demo.phenixid.net to your LB/reverse proxy configuration (Access-Control-Allow-Origin header).
You can also copy-paste the source code of the html and deploy in in your own environment. In that case, also remember to change the Access-Control-Allow-Origin configuration to match the protocol://domain:port of the web server hosting the html page.