Cross-origin issues in JWT auth

Hi all,

I am expanding on the latest play-silhouette 6.0.0 seed.
Went multi-environment by adding a JWT environment, everything works fine.

When I test localhost:9000 from within IntelliJ I get all the correct replies.
Now I am testing the API calls from an application served from localhost:8080 and I get:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:9000/api/auth/signin. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:9000/api/auth/signin. (Reason: CORS request did not succeed).

The same application has no problems connecting to a different server on localhost:9000, so I guess the issue is configuration of my silhouette seed.

I have the following settings (secrets excluded) in silhouette.conf:

  # Authenticator settings (exactly from the seed)
  authenticator.cookieName = "authenticator"
  authenticator.cookiePath = "/"
  authenticator.secureCookie = false // Disabled for testing on localhost without SSL, otherwise cookie couldn't be set
  authenticator.httpOnlyCookie = true
  authenticator.sameSite = "Lax"
  authenticator.useFingerprinting = true
  authenticator.authenticatorIdleTimeout = 30 minutes
  authenticator.authenticatorExpiry = 12 hours
  authenticator.rememberMe.cookieMaxAge = 30 days
  authenticator.rememberMe.authenticatorIdleTimeout = 5 days
  authenticator.rememberMe.authenticatorExpiry = 30 days

  # JWT Authenticator additional settings (added)
  authenticator.fieldName = "X-Auth-Token"
  authenticator.requestParts = ["headers"]
  authenticator.issuerClaim = "Your fancy app"

Anyone can point me in the right direction on how to correctly solve this?

Thank you in advance

Hi,

CORS issues have generally not to do with Silhouette. Your problem comes from the fact that your browser sends a request to a server that isn’t the same. If you send a request from an app on port 8080 to the Play application that runs on port 9000, then for the browser that isn’t the same origin.

In your case you should add the CORS filter and allow your origin http://localhost:8080:
https://www.playframework.com/documentation/2.7.x/CorsFilter

Best regards,
Christian

Hi Akkie,

thank you for your quick reply.

I added to application.conf

play.filters.enabled += "play.filters.cors.CORSFilter"

# CORS filter configuration
play.filters.cors {
  # The allowed origins. If null, all origins are allowed.
  allowedOrigins = null
}

But when I make the request, Firefox sends the following preflight and it is still not allowed. By the way, there is something specific that has to be done for preflights? (I thought no)

The following is the HAR:

{
  "log": {
    "version": "1.2",
    "creator": {
      "name": "Firefox",
      "version": "67.0.2"
    },
    "browser": {
      "name": "Firefox",
      "version": "67.0.2"
    },
    "pages": [
      {
        "startedDateTime": "2019-06-19T15:59:27.610+02:00",
        "id": "page_1",
        "pageTimings": {
          "onContentLoad": -1,
          "onLoad": -1
        }
      }
    ],
    "entries": [
      {
        "pageref": "page_1",
        "startedDateTime": "2019-06-19T15:59:27.610+02:00",
        "request": {
          "bodySize": 0,
          "method": "OPTIONS",
          "url": "http://localhost:9000/api/auth/signin",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "Host",
              "value": "localhost:9000"
            },
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
            },
            {
              "name": "Accept",
              "value": "*/*"
            },
            {
              "name": "Accept-Language",
              "value": "en-US,en;q=0.5"
            },
            {
              "name": "Accept-Encoding",
              "value": "gzip, deflate"
            },
            {
              "name": "Access-Control-Request-Method",
              "value": "POST"
            },
            {
              "name": "Access-Control-Request-Headers",
              "value": "content-type"
            },
            {
              "name": "Referer",
              "value": "http://localhost:8080/"
            },
            {
              "name": "Origin",
              "value": "http://localhost:8080"
            },
            {
              "name": "DNT",
              "value": "1"
            },
            {
              "name": "Connection",
              "value": "keep-alive"
            }
          ],
          "cookies": [],
          "queryString": [],
          "headersSize": 406
        },
        "response": {
          "status": 404,
          "statusText": "Not Found",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "Referrer-Policy",
              "value": "origin-when-cross-origin, strict-origin-when-cross-origin"
            },
            {
              "name": "X-Frame-Options",
              "value": "DENY"
            },
            {
              "name": "X-XSS-Protection",
              "value": "1; mode=block"
            },
            {
              "name": "X-Content-Type-Options",
              "value": "nosniff"
            },
            {
              "name": "Content-Security-Policy",
              "value": "default-src 'self'; img-src 'self' *.fbcdn.net *.twimg.com *.googleusercontent.com *.xingassets.com vk.com *.yimg.com secure.gravatar.com; style-src 'self' 'unsafe-inline' cdnjs.cloudflare.com maxcdn.bootstrapcdn.com cdn.jsdelivr.net fonts.googleapis.com; font-src 'self' fonts.gstatic.com fonts.googleapis.com cdnjs.cloudflare.com; script-src 'self' cdnjs.cloudflare.com; connect-src 'self' twitter.com *.xing.com;"
            },
            {
              "name": "X-Permitted-Cross-Domain-Policies",
              "value": "master-only"
            },
            {
              "name": "Date",
              "value": "Wed, 19 Jun 2019 13:59:29 GMT"
            },
            {
              "name": "Content-Type",
              "value": "text/html; charset=UTF-8"
            },
            {
              "name": "Content-Length",
              "value": "1152"
            }
          ],
          "cookies": [],
          "content": {
            "mimeType": "text/html; charset=UTF-8",
            "size": 0,
            "text": ""
          },
          "redirectURL": "",
          "headersSize": 780,
          "bodySize": 780
        },
        "cache": {},
        "timings": {
          "blocked": 0,
          "dns": 0,
          "connect": 0,
          "ssl": 0,
          "send": 0,
          "wait": 1500,
          "receive": 0
        },
        "time": 1500,
        "_securityState": "insecure",
        "serverIPAddress": "127.0.0.1",
        "connection": "9000"
      }
    ]
  }
}

I have another (copied) JWT app based on Silhouette 5.0 / Play 2.6 that is working fine, allowing the same request that here is blocked, but is not obvious to me what is doing different.
It has:

play.filters.headers.frameOptions="ALLOW-FROM http://*"
play.filters.headers.contentSecurityPolicy="frame-src * ;"
play.filters.enabled += "play.filters.cors.CORSFilter"
play.filters.disabled += play.filters.csrf.CSRFFilter

On the working Silhouette 5.0 / Play 2.6 server the preflight HAR (originated from the same request and accepted with status 200) is:

{
  "log": {
    "version": "1.2",
    "creator": {
      "name": "Firefox",
      "version": "67.0.2"
    },
    "browser": {
      "name": "Firefox",
      "version": "67.0.2"
    },
    "pages": [
      {
        "startedDateTime": "2019-06-19T16:28:42.933+02:00",
        "id": "page_3",
        "pageTimings": {
          "onContentLoad": -1,
          "onLoad": -1
        }
      }
    ],
    "entries": [
      {
        "pageref": "page_3",
        "startedDateTime": "2019-06-19T16:28:42.933+02:00",
        "request": {
          "bodySize": 0,
          "method": "OPTIONS",
          "url": "http://localhost:9000/api/auth/signin",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "Host",
              "value": "localhost:9000"
            },
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
            },
            {
              "name": "Accept",
              "value": "*/*"
            },
            {
              "name": "Accept-Language",
              "value": "en-US,en;q=0.5"
            },
            {
              "name": "Accept-Encoding",
              "value": "gzip, deflate"
            },
            {
              "name": "Access-Control-Request-Method",
              "value": "POST"
            },
            {
              "name": "Access-Control-Request-Headers",
              "value": "content-type"
            },
            {
              "name": "Referer",
              "value": "http://localhost:8080/"
            },
            {
              "name": "Origin",
              "value": "http://localhost:8080"
            },
            {
              "name": "DNT",
              "value": "1"
            },
            {
              "name": "Connection",
              "value": "keep-alive"
            }
          ],
          "cookies": [],
          "queryString": [],
          "headersSize": 406
        },
        "response": {
          "status": 200,
          "statusText": "OK",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "Vary",
              "value": "Origin"
            },
            {
              "name": "Referrer-Policy",
              "value": "origin-when-cross-origin, strict-origin-when-cross-origin"
            },
            {
              "name": "X-Frame-Options",
              "value": "ALLOW-FROM http://*"
            },
            {
              "name": "X-XSS-Protection",
              "value": "1; mode=block"
            },
            {
              "name": "Access-Control-Max-Age",
              "value": "3600"
            },
            {
              "name": "X-Content-Type-Options",
              "value": "nosniff"
            },
            {
              "name": "Content-Security-Policy",
              "value": "frame-src * ;"
            },
            {
              "name": "Access-Control-Allow-Origin",
              "value": "http://localhost:8080"
            },
            {
              "name": "Access-Control-Allow-Headers",
              "value": "content-type"
            },
            {
              "name": "Access-Control-Allow-Methods",
              "value": "POST"
            },
            {
              "name": "Access-Control-Allow-Credentials",
              "value": "true"
            },
            {
              "name": "X-Permitted-Cross-Domain-Policies",
              "value": "master-only"
            },
            {
              "name": "Date",
              "value": "Wed, 19 Jun 2019 14:28:43 GMT"
            },
            {
              "name": "Content-Length",
              "value": "0"
            }
          ],
          "cookies": [],
          "content": {
            "mimeType": "text/plain",
            "size": 0,
            "text": ""
          },
          "redirectURL": "",
          "headersSize": 559,
          "bodySize": 559
        },
        "cache": {},
        "timings": {
          "blocked": 0,
          "dns": 0,
          "connect": 0,
          "ssl": 0,
          "send": 0,
          "wait": 499,
          "receive": 0
        },
        "time": 499,
        "_securityState": "insecure",
        "serverIPAddress": "127.0.0.1",
        "connection": "9000"
      },
      {
        "pageref": "page_3",
        "startedDateTime": "2019-06-19T16:28:43.476+02:00",
        "request": {
          "bodySize": 51,
          "method": "POST",
          "url": "http://localhost:9000/api/auth/signin",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "Host",
              "value": "localhost:9000"
            },
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
            },
            {
              "name": "Accept",
              "value": "application/json, text/plain, */*"
            },
            {
              "name": "Accept-Language",
              "value": "en-US,en;q=0.5"
            },
            {
              "name": "Accept-Encoding",
              "value": "gzip, deflate"
            },
            {
              "name": "Referer",
              "value": "http://localhost:8080/"
            },
            {
              "name": "Content-Type",
              "value": "application/json;charset=utf-8"
            },
            {
              "name": "Content-Length",
              "value": "51"
            },
            {
              "name": "Origin",
              "value": "http://localhost:8080"
            },
            {
              "name": "DNT",
              "value": "1"
            },
            {
              "name": "Connection",
              "value": "keep-alive"
            }
          ],
          "cookies": [],
          "queryString": [],
          "headersSize": 416,
          "postData": {
            "mimeType": "application/json;charset=utf-8",
            "params": [],
            "text": "{\"password\":\"kasslsjjkjs\",\"email\":\"sss.ada@ss.com\"}"
          }
        },
        "response": {
          "status": 404,
          "statusText": "Not Found",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "Vary",
              "value": "Origin"
            },
            {
              "name": "Referrer-Policy",
              "value": "origin-when-cross-origin, strict-origin-when-cross-origin"
            },
            {
              "name": "X-Frame-Options",
              "value": "ALLOW-FROM http://*"
            },
            {
              "name": "X-XSS-Protection",
              "value": "1; mode=block"
            },
            {
              "name": "X-Content-Type-Options",
              "value": "nosniff"
            },
            {
              "name": "Content-Security-Policy",
              "value": "frame-src * ;"
            },
            {
              "name": "Access-Control-Allow-Origin",
              "value": "http://localhost:8080"
            },
            {
              "name": "Access-Control-Allow-Credentials",
              "value": "true"
            },
            {
              "name": "X-Permitted-Cross-Domain-Policies",
              "value": "master-only"
            },
            {
              "name": "Date",
              "value": "Wed, 19 Jun 2019 14:28:43 GMT"
            },
            {
              "name": "Content-Type",
              "value": "text/html; charset=UTF-8"
            },
            {
              "name": "Content-Length",
              "value": "1131"
            }
          ],
          "cookies": [],
          "content": {
            "mimeType": "text/html; charset=UTF-8",
            "size": 1131,
            "text": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <title>Not Found</title>\n        <style>\n            html, body, pre {\n                margin: 0;\n                padding: 0;\n                font-family: Monaco, 'Lucida Console', monospace;\n                background: #ECECEC;\n            }\n            h1 {\n                margin: 0;\n                background: #AD632A;\n                padding: 20px 45px;\n                color: #fff;\n                text-shadow: 1px 1px 1px rgba(0,0,0,.3);\n                border-bottom: 1px solid #9F5805;\n                font-size: 28px;\n            }\n            p#detail {\n                margin: 0;\n                padding: 15px 45px;\n                background: #F6A960;\n                border-top: 4px solid #D29052;\n                color: #733512;\n                text-shadow: 1px 1px 1px rgba(255,255,255,.3);\n                font-size: 14px;\n                border-bottom: 1px solid #BA7F5B;\n            }\n        </style>\n    </head>\n    <body>\n        <h1>Not Found</h1>\n\n        <p id=\"detail\">\n            For request 'POST /api/auth/signin'\n        </p>\n\n    </body>\n</html>\n"
          },
          "redirectURL": "",
          "headersSize": 499,
          "bodySize": 1630
        },
        "cache": {},
        "timings": {
          "blocked": 0,
          "dns": 0,
          "connect": 0,
          "ssl": 0,
          "send": 0,
          "wait": 52,
          "receive": 0
        },
        "time": 52,
        "_securityState": "insecure",
        "serverIPAddress": "127.0.0.1",
        "connection": "9000"
      }
    ]
  }
}

I understood this is more of a Play issue, but if you have any idea I would greatly appreciate your input.

Thank you

Ok, I discovered something, maybe interesting.

CORS seems to work if in application.conf:

  1. play.filters.enabled += "play.filters.cors.CORSFilter" is added
  2. play.http.filters = "utils.Filters" is removed

In your opinion, is 2 needed in the seed example or is maybe a relic from previous Play versions?

Hi,

The seed adds the filters programmatically. You can add the CORS filter in the utils.Filter class. The class adds already other filters. If you use your first approach, then you should add the other filters (from utils.Filter) too to your configuration.

Thank you very much for the clarification, understood. Added to utils.Filter and it is working fine.