I'd like to build an app that'll support multiple platforms: Desktop applications (Mac/PC), Web (angularJS fronted) and native mobile apps.
So I'm thinking of an application server, serving internal APIs to the platforms above. I have certain assumptions regarding how login/logouts are to be supported. I would be happy if anyone could comment if my thinking is wrong.
You have the basic structure correct, however with OAuth2 you will never be storing the access token forever. The access token is often an opaque string that grants access to your API, storing it in a cookie or local storage is fine, but issuing a token from the server that never expires would be highly inadvisable (a MITM attack could jack your identity forever).
To solve this issue, OAuth2 implementations typically dole out refresh tokens alongside access tokens. A refresh token will typically have a longer expiration timeframe than an access token (anywhere between the expiration time of the access token and a month I would say). Refresh tokens are akin to a temporary user password - they do not grant any access to your API directly, however with one a user can authorize with your system via calling your OAuth2 refresh api, and get back fresh access and refresh tokens with new expiration time. This gives your application a chance to revalidate the users claims regularly (maybe their access / role has changed and they need updated claims).
Access tokens may be opaque strings that you store on the server, however I would highly recommend using JWT tokens. JWT tokens have 2 major benefits over opaque (meaningless) tokens:
1. Client Claims
The first thing you are going to need to do in your client application post-authorization is look up all kinds of stuff to build your UI. The beauty of JWT tokens is that they store all of your users claims (including your apps custom user claims) as a JSON object payload inside an encoded string which can be base 64 decoded by the client. This allows your client application to decode the users claims atomically from nothing more than an access token vs. the traditional model of using the access token to go and look up information about the user. With JWT you will immediately know the users first name, last name, user ID, and any other data you would like to stick in the JWT token.
To use your authorized API, your client application will make requests to endpoints and send an
'Authorization': 'Bearer access_token' (where
access_token is your access token). In traditional apps, the access token must be looked up server side to verify that the server granted it. The other awesome bit about JWT tokens is that when they are issued there is a server side secret that is used to sign them. When the server needs to verify them it simply uses the server side secret to sign them and if it passes, the server will grant the API request based on the claims of the decoded token. There is no need to store them server side making your architecture much simpler. The server has no need to talk to a database on every authorized API request. You will be bypassing many issues such as synchronizing access tokens across web farms or storing them at all (you will however still need to store the refresh tokens in a table linked to the user).
A pretty common misconception people have about cookies is that they should be avoided because they open you up to
CSRF attacks, however this is often not the case. Systems like web forms that send session cookies and hydrate session based on those cookies are open to CSRF attacks but if you are building a single page application and all of your security checkpoints are at your API layer, your endpoints will be checking the
Authorization header for your bearer token, not the cookies. If you are doing server side rendering and using the cookies value there, you should be aware of CSRF threats and implement methods of prevention. If you go with cookies, you should ensure they do not have the HttpOnly flag set and do have the Secure flag set to protect you from MITM threats.
Since you are using node (or angular at least), I will now plug a library I wrote - jwt-autorefresh. The point of this library is simple, give it your apps refresh mechanism (the client code that makes the http request to your refresh api and subsequently stores results in a cookie) and the number of seconds that you want to have your tokens refreshed prior to their expiration, and it will handle auto scheduling refresh on your client application. Internally it decodes your JWT token and looks at its
exp claim (expiration time) to figure out how far out it needs to schedule your refresh. It has features such as adding a small amount of jitter to the refresh time so that all client instances will not attempt refreshing simultaneously.