0

I have 3 website projects as follows;

  1. identity.example.com (asp.netcore + IdentityServer4)
  2. api.example.com (asp.netcore webapi)
  3. www.example.com (asp.netcore + aurelia)

I am able to authenticate the user using SPA user-agent using implicit grant type and I store the access_token on localStorage. Since localStorage is not safe, long lived access_token or use of refresh_token is not safe, so my users will have to login every X minutes which is not acceptable as you would agree.

My goal is to let users have "remember me" option and still keep things as secure as acceptable (I know there is no 100% security in this regard)

I think I have the following options;

1. Full Proxy Between SPA and API/IdentityServer, everything stored as http_only cookie

  • spa posts credentials to its own backend (www.example.com/login)
  • the spa's backend gets access_token and refresh_token from identity.example.com, stores the tokens in http_only cookie or in the session.
  • the spa's backend acts as a proxy between the api and the spa andthe spa makes any api calls to it's own backend, not to the api
  • the spa's backend routes all the api requests to api.example.com using the access_token stored in the cookie/session and returns the result to the user_agent.

Now this approach looks secure but it adds a proxy level which would have negative effect on performance, request response times etc. Plus, I would need to write a wrapper/proxy for every single api method in the spa's backend (the proxy) that would make calls to the actual api methods which is not that cool.

I thought of the following flow to make it secure and not painful for the user at the same time;

2. Semi Proxy Between SPA and Identity Server (for refreshing tokens only)

Summary: SPA has access to access_token, However, to refresh the token, spa should interact with its backend which holds the refresh_token in session/cookie securely.

  • spa posts credentials to its own backend (www.example.com/login)
  • spa gets an authorization_code from the identity server (client_secret is not neccessary for this) and posts this to its own backend
  • the spa's backend gets access_token and refresh_token from identity.example.com using the authorization_code, stores the tokens in http_only cookie or in the session and also includes the access_token in the response body
  • the spa stores the access_token in its localStorage, and uses this for calls directly to the api (api.example.com, no proxy)
  • when the access_token expires, the user-agent (spa) asks it's own backend to refresh the access_token on behalf of itself
  • the spa's backend retrieves the new access_token and refresh_token from the identity server. Updates the tokens in the session/cookie and returns the access_token to the user_agent (spa)
  • the user_agent (spa) updates the access_token in its localStorage and uses this for future calls to the api.

    Now, the questions... Is the last flow I have described secure and acceptable by the standards? Do you know an example implementation of it? Or do you have other suggestions that I should follow? Maybe security flaws I should consider?

Hasan
  • 101
  • 4
  • 1
    `Since localStorage is not safe` not safe against who? What's your threat model? Also, I don't think you're implementing an implicit grant. Proper implicit grant, your client application redirect user to identity.example.com rather than collecting the credential from the user itself. The entire OAuth premise is that applications and resource server never need to deal with the user's actual username/password. – Lie Ryan Apr 02 '17 at 07:53
  • @LieRyan thanks for the comment. localStorage is not safe against the user (the user can access the client secrets and the tokens). I am currently using implicit grant. What I am trying to do is to have another flow/grant to be able to get refresh_tokens because IdentityServer4 doesn't provide refresh_tokens for the implicit grant type. Yes I am aware the two "solutions" I proposed are not using the implicit grant flow – Hasan Apr 02 '17 at 08:07
  • @LieRyan Maybe I am overcomplicating things. All I want to do is to be able to use refresh_tokens without going against agreed standards and having security related troubles – Hasan Apr 02 '17 at 08:12
  • 2
    There are no authentication mechanism where the user cannot get the token that their user-agent uses to authenticate against the service. It's impossible to build one. Cookies, HTTP only Cookies, local storage, session storage, flash supercookies, etc; user can always get the token because the token is in the user's machine. – Lie Ryan Apr 02 '17 at 09:28
  • I understand your reasoning in first scenario. You don't want to deal with CORS and use single origin policy to your advantage. What I don't understand with your second secnario is that you abandoned the original idea and you now need to have CORS fully configured. I don't really see the point in delegating only token handling to SPA backend. What would you achieve with this? If you don't want to deal with CORS and want to make use of SOP but still have API on separate domain, you can configure reverse proxy inside your backend using Application Request Routing for IIS. – Marko Vodopija Apr 03 '17 at 10:32
  • @MarkoVodopija First option introduces use of an additional (proxy) layer for every request. I feel like this would have negative impact on request times as the requests/responses will have to jump through another hoop (proxy). The second option will need CORS as you said. But it's advantage on the first option is, there is no proxy layer between the client (SPA) and the API and I still get to hide my client secret and refresh token from anybody. I don't know the dangers of using CORS though. What do you think which one is more feasible? – Hasan Apr 03 '17 at 10:39
  • Also, I actually implemented the second option currently and it is working well. I enabled CORS only for the SPA's domain (www.example.com), this should protect me from the dangers of CORS, no? – Hasan Apr 03 '17 at 10:41
  • 1
    Introducing a reverse proxy will not incur dramatic performance penalty if that is your only concern. I tested the scenario with RP between Tomcat app server and IIS as frontend proxy. Incurred penalty was between 2-5ms in total response time. As for CORS, I don't feel comfortable giving advice on how to configure it. I, personally, would avoid CORS if I could. In scenarios where I control complete stack, I would configure it in such way that CORS is not needed. Using reverse proxy is one of the options. If CORS is your real concern, I would advice to post new question and be more specific. – Marko Vodopija Apr 03 '17 at 13:05

1 Answers1

0

Just wondering whether all the projects need to be separate since running on the same hosting. If you remove the Identity Server and combine the API and Aurelia into one project with a single well known authentication provider you can use Microsoft Identity to authenticate and authorize the SPA using the Home page of the Web Site (with some Scaffolding of Razor pages). Default setting use SQL Server Express and a simple migration generates all the Identity tables. The API is therefore only required when the user is logged on so no need for tokens or the horrific structure needed to maintain a token identity server to produce a complex single sign on as the authentication is maintained by Identity out of the box (Net Core 2.1)