GetAccessToken response including access-token

(continuation of conversation of this pull request https://github.com/ServiceStack/ServiceStack/pull/1248)

We have done some more deep dive security reviews on this, and I wanted to open a discussion on how the JwtAuthProvider works as it is now, in a specific scenario, and how it works together with the GetSessionTokenService, and see what can be done.

In the specific scenario we are looking at, we use httponly cookies to maintain a browser “session” between a JSApp (React) and a WebServer (ServiceStack) where we host the AuthenticateService and the GetAccessTokenService services.

The JSApp obtains a JWT from the webserver by passing username+password to a CredentialsAuthProvider+ JwtAuthProvider. The result is a response like this, and we want the ss-reftok cookie as well.

"userId": "user_wAXnNtUtQEeZJAenWaeKNQ",
    "sessionId": "gLc2Jesk2zg3fvGIfS1o",
    "userName": "auser@company.com",
    "displayName": "afirstname alastname",
    "bearerToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InljTCJ9.eyJpc3MiOiJzc2p3dCIsInN1YiI6InVzZXJfd0FYbk50VXRRRWVaSkFlbldhZUtOUSIsImlhdCI6MTYyODEzODcyOCwiZXhwIjoxNjMwNzMwNzI4LCJlbWFpbCI6ImF1c2VyQGNvbXBhbnkuY29tIiwiZ2l2ZW5fbmFtZSI6ImFmaXJzdG5hbWUiLCJmYW1pbHlfbmFtZSI6ImFsYXN0bmFtZSIsIm5hbWUiOiJhZmlyc3RuYW1lIGFsYXN0bmFtZSIsInJvbGVzIjpbInN0YW5kYXJkIl0sImp0aSI6MX0.SADZP96CO6m0XFwTsiBE47AvB5zqJcgSFf5g-WbEhyGj0J95uNXh4OY2ge61BZ3WShJChJuwmDDzTHqFmxuReC0qvGD5FcBihSemQuoYJ6Uz0mL_dGpH7LksCCBNEX3fOXFEkSB9EQ_o80CMrTfYFJiFmoHZils4d315HEqRm4ttiBH51QcvInWbANQ-rYiRJxbQhdoDWj5J7v4ox_BVke-n6kZpH02l1svjPuTbdomu3blWjprJvJZWLEwFVCOmJTkFbXMs0UUqzaL6Dzw0l1TvmXIykOtMxCYLWpXzdlItS8M56fBZclsi48UndUm62C2aEI2OqI_btdp5neIFhg",
    "refreshToken": "eyJ0eXAiOiJKV1RSIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJ5Y0wifQ.eyJzdWIiOiJ1c2VyX3dBWG5OdFV0UUVlWkpBZW5XYWVLTlEiLCJpYXQiOjE2MjgxMzg3MjgsImV4cCI6MTY1OTY3NDcyOCwianRpIjotMX0.aq9P75EuE2_IB-Veansl_0JyIo7DwxfUJFG07pwtx3HGVAhGIMJMkY2QudhFr89x6g_4NhfvZcOVT1rFQXOeS_GxGngpstTPfQnmlUzs3HGt0f7uGsCBkl7AB59m0v7Zo5m6vlfZIn2kH_PaTi8BQ1ecpDPF6Mx6eBerOGt2JIJCIDTZELVKdjq9YTDZNoy8IwsJU1r0NWnQd6lYPNo1VymDuCzZhbUTAHJSwWO9g-Pajf21qIoYB8rrO5vYeaYArEJDChJ57qxlJGAChkVthlC6hRlZzvpI3VsOT1smI1aMtXMXc2LW7wR_bfoNRfVy0TXTuubVPENg6PemHe1p1w",

At some time later, when the access_token value has expired (or close to it), we want to make a call from the JSApp to the webserver passing only the ss-reftok cookie, and receiving: a new ss-reftok cookie and receiving a new access_token from the response JSON, like this:

{
    "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InljTCJ9.eyJpc3MiOiJzc2p3dCIsInN1YiI6InVzZXJfd0FYbk50VXRRRWVaSkFlbldhZUtOUSIsImlhdCI6MTYyODEzODk0OCwiZXhwIjoxNjMwNzMwOTQ4LCJlbWFpbCI6ImF1c2VyQGNvbXBhbnkuY29tIiwiZ2l2ZW5fbmFtZSI6ImFmaXJzdG5hbWUiLCJmYW1pbHlfbmFtZSI6ImFsYXN0bmFtZSIsIm5hbWUiOiJhZmlyc3RuYW1lIGFsYXN0bmFtZSIsInJvbGVzIjpbInN0YW5kYXJkIl0sImp0aSI6Mn0.MZwvADjIi0x-4nNY1zOGoj3G6NvzoPKAz8bYQ1ZqjWoFIcCTDPeHcpSBGiQzO-pq4iBoQKNaedX9EWO90d_rHRJepd6Sb5YoV69z1AICaiQ8j4z3trUjE1-du9XiydqWdZiN3k-DSpTxG_6o9btatoNeBca-5xKHzDa4JTTJPRA7hUlgU3iMW6WQlmUz8YgGeb2UKyVD25UppCv5Fmy3dU9V11W9AlbTE6ioVoue_fEjmJLInhF9FCn0xl5AbOQ_yTluZDug3uNkJfMO7t-WSaYXWt7QPwECljaiJYgg2AkT89pwTVkPL5qkFBSlKzNk7t2Yoz1R0oxTOI1AzKfuug"
}

In this scheme, we need the ss-reftok cookie to maintain a login state between the browser and server for longer than the 15mins long access_token expiry - the the week long expiry of the ss-reftok.

So, given the answer in the pull request, which we accept, we would then hope to only turn ON ss-reftok with jwtAuthProvider.UseRefreshTokenCookie= true and turn OFF ss-tok with jwtAuthProvider.UseTokenCookie= false, to get our access_token in the JSON of the response to POST /access-token.

Otherwise, we have no way of getting the actual access_token value into the JSApp (for later use with an API) AND at the same time keep the cookie session connection between browser and webserver to preserve the login between browser sessions.

Would you agree with us that this should be possible securely?

Think about what a XSS script running in your page could do if they are able to make an Ajax call and retrieve the BearerToken in a plain text body? They could forward it to their remote server which would then be able to make indiscriminate Authenticated calls with that Bearer Token.

Any solution that involves either storing the Bearer Token in a JS App or allows them to make an Ajax call to retrieve a Bearer Token is going to be a security risk. By only using Secure HttpOnly Token Cookies the JS App will never be able to gain access to the BearerToken, which is why they’re recommended for Web Apps.

If you do go forward with a solution that does this, be aware that it’s going to be susceptible to XSS attacks.

I’m still not clear why you actually need the BearerToken in the JS App given the JWT Auth Provider supports Refresh Token Cookies? The JS/TypeScript JsonServiceClient automatically refreshes the BearerToken Cookie when it expires, which allows you to make authenticated calls + auto refreshes expired JWT’s without the JS App needing to store either Bearer or Refresh Tokens.

Yes agreed. Any XSS and all bets are off, no matter what. That’s the assumption we are working under.

In our design, the JSApp gets the bearer from the webserver and uses it to talk to other APIs on other domains. Simple as that really. That’s why the JSApp has the access_token in memory.

If you can use a reverse proxy you’ll be able to keep the same external domain and Token Cookies so the server makes the cross-domain calls instead of the client.

OK cool, thanks for the prompt.
Just to tie this up for others coming later.

So what we can do then is this.

Instead of: passing the access_token in the response body to the JsApp (after Authenticate and GetAccessToken), storing it in browser memory and using it later to call APIs in other domains, and refreshing it using ss-reftok cookies. (Which can be a high risk with successful XSS attacks getting to our in-memory access_token).

We can instead, maintain the ss-tok and ss-reftok cookies between JsApp and WebServer. Then proxy all external API calls through the WebServer itself (using ProxyFeature) which adds the access_token (from ss-tok cookie) into a bearer header, (and that also handles automatic token refresh using ss-reftok).
We then, critically have to take care of CSRF attacks, as forced-browsing is now the main issue.

We can do in a couple of ways, but in our case of a SPA we don’t have a lot of HTML forms to deal with, so we will protect against CSRF using Origin headers, as per OWASP cheatsheet

2 Likes