107

I have a requirement to implement Facebook and Google login in my web application. I also need to access a user's Facebook/Google+ friend list. I have gone through the complete OAuth2 documentation of Facebook and Google. I understood the basic concept. For example, lets say for Facebook login the steps are:

  1. The user will click on "FB Login" button.
  2. The user will be asked to login to Facebook and allow permission. If user allows it will return an authorization code.
  3. Now we will use the authorization code to get an access token.
  4. We can store the access token in session to start a user session.
  5. Now we can use the access token to access to different user resources.

Now I have some confusion after step 3. Should we generate access token each time the user logs in or store the access token in our DB?

If we store the access token in our DB, how we can reuse it when a user comes to our site after 10 days (let's say he cleared the browser cookies) and click on "FB Login" button again. Because when user clicks the 'FB Login' button again he will get a new authorization code and have to start the complete process again. How can I recognize that this user already has an access token in my DB?

Any help would be greatly appreciated.

Flimzy
  • 655
  • 1
  • 6
  • 14
Deepak Kumar Padhy
  • 1,178
  • 2
  • 8
  • 7
  • As a side note, one thing to strongly consider is the security related to the access token. I hope it is not stored in plain text (e.g. unencrypted). – Jonathan Nov 07 '14 at 15:41
  • I think your last question was not answered: "How can I recognize that this user already has an access token in my DB?" – Dherik Jun 29 '15 at 16:33

5 Answers5

42

Technically you can store the access token in your database, and use it for API calls until it expires. It might be more trouble than its worth, though.

For one thing, as Jonathan notes in his comment above, now you have to worry about securing your database and the data in it - these tokens give access to some fairly privileged information about your users. Of course, simply storing the token in session storage might put it on disk too, depending on your session configuration. Its a good idea to keep it encrypted while you're not using it.

Your proposed scenario about the user clearing cookies and coming back is also an issue. You could take the access token from the database and stick it back into their cookies, but before you do that, you have to make sure they are who they say they are - and now you have to do another layer of passwords just to give them access to the token they already gave you.

You're probably better off simply re-doing the authorization flow when they come back and click the login button again. Its not that expensive. But if that truly is a showstopper for you, then storing the token is an option. You'll just have to be really careful about working through all the associated issues.

Bruce
  • 551
  • 3
  • 4
  • I have the exact same issue. The problem is, every time they log in, new OAuth session is established. After 20 logins, there will be 20 "loose" sessions open. If you go to a page listing authorized tokens on your service provider's site, it looks like headache. How do you deal with that? – Konrad Garus Oct 30 '15 at 09:28
  • @Konrad Garus Most providers like facebook or google will correlate sessions for a single application into a single entry and invalidate older access tokens on login. – Jonas Köritz May 08 '17 at 11:21
15

I've been thinking about this and may have come up with an answer that will work for us, though I can't say whether it would work for you.

In our environment, the main reason we might need to use access tokens is to operate on behalf of a user after some automated or backend process is complete, or on a schedule. In such a scenario we can't simply ask the user to log in again because the user isn't involved in this workflow directly (he would have requested the work to be done but isn't there when it completes). So we have to have access to the access token somehow.

Of course, I'd like to skip reauthorization where feasible too, if it doesn't cause problems.

But I also don't like the idea of storing everything needed to use the access token on the web server. So even if I encrypt the access token in the database, it doesn't really soothe my fears if the encryption key is stored on the web server. Heck, the client secret and app ID are there, too, so that's everything.

So here's my proposed solution, which requires four actors:

The web server stores the appid, secret, and database connection string (of course). When it gets an app token, it generates a random symmetric encryption key and encrypts the access token. The database gets the encrypted access token and the encryption key is stored in a client cookie. At the same time, the web server sends the encryption key and the user ID to the backend system as a pair.

When the client is using the site, reauthentication can be avoided by using the client cookie to decrypt the access token in the database; if the cookie goes away, reauthentication has to happen no matter what. The backend system (which would have a much smaller attack surface than the web server) also has a DB connection string, so it's the only place where all necessary information would reside to interact with user information; it could use the information at will for the life of the access token.

This leaves the web server with only transient access to any access token, and the token is never stored on the client. It seems quite secure to me, though maybe some would say it's over-engineered. Thoughts?

Dominic P
  • 251
  • 1
  • 3
  • What is the difference between your *web server* actor and the *backend system" actor? If they both are programs running in a remote box which is out of reach of anyone except the system admin, then they are all considered as "[confidential clients](https://tools.ietf.org/html/rfc6749#section-2.1)" by definition. So you do not really have to separate them. Or perhaps you just think a web server opens a port and listens to traffic, while a backend system does not and hence theoretically more secure? – RayLuo Feb 08 '17 at 01:13
  • Basically you've got it right. In my environment, the web application gathers information about compute-intensive jobs and places the requests for processing by the backend (which the only place the access token needed to be used absent a client request) in a message queue. The backend system is a complete island with no inbound access, pulling requests from the queue, so it should be highly secure. Even a complete compromise of the web server would provide little way of attacking the backend. (The attacker could, of course, harvest access tokens over time as users logged in again.) – Dominic P Feb 13 '17 at 17:59
  • I don't quite understand the difference between the "web server" and the "backend system" either. I like your proposal though, about keeping the encryption key as a cookie. The only issue for us is that at times, we will access the user data without the user being active, with your proposed solution, that would no longer be possible. – dearsina May 23 '20 at 12:31
  • I like this solution, really clever, however, if a client is accessing the server through multiple clients (Mobile App, Web App, ...), then this solution does not work since access_token only can be read for one "client" at a time (since the key is stored in a client cookie). – Rochadsouza May 31 '20 at 15:30
  • @dearsina Our scenario relied on an automated worker node processing a queue message, potentially days or weeks after the user interacted with the site, to fulfill a user request. This worker node had practically no attack surface, so giving it persistent access to user data via the access token was acceptable, while we didn't want to allow that on the web server. (Edit: snipped response to wrong comment here) You could still access user information while the user is not active, you'd just need to do it from the "backend node"-- a different process isolated from the web server. – Dominic P Jun 22 '20 at 04:30
  • @Rochadsouza For your situation, you would need to store an access token per user device-- probably acceptable, since the user would need to authenticate the first time on each device anyway. – Dominic P Jun 22 '20 at 04:32
3

I think there is some confusion about the access token and how it is used which can cause a security problem.

It is correct that the tokens can be a security risk, but this depends on the information you are asking for from the service. A friends list is more information than first and last name for instance, but that information is not as vital as personal information. In either case, you NEVER want to expose the actual access token, because it is like exposing a password to your application.

I choose to make a secondary 'token', if you will, which holds a session value (eg. an encrypted session value in cookies) that identifies the user until it expires. So I have the access token in the database (should probably be encrypted, just to be safe) that can access the user information.

You can also retrieve the ID of the person through the token. If you at least store this in the database, you can match the retrieved token through the ID of the person. That way when you exchange a code for the access token, you can compare the ID's and find the right record.

Mark
  • 71
  • 2
2

A lot of these answers are outdated as they were written before the widespread adoption of JSON Web Tokens (JWT).

You should treat an access_token as good as having an email/password pair in hand, so it needs to be stored and transmitted securely. An access_token should not contain too much information, just a user ID in the sub scope of a JWT is enough. Since JWT prevents malicious modifications of the payload, you can safely use the grant and ID stored there.

Storing an access_token on a server may be a bit heavy handed for most applications since you can just use a short expiry on your access_tokens, storing a refresh_token instead (less frequent DB calls). If you need user info, store that in an ID Token and only use it for displaying information about the authenticated user.

Re-request the ID Token whenever you want to refresh that info and NEVER submit an id_token to an API.

Nowadays, smart developers implement OpenID Connect to standardize their user info and access grants on top of OAuth 2.0 and I would start there as most Identify Providers (IdP) implement this spec (i.e. Google, Facebook, Auth0, YourServiceHere).

1

I would recommend storing tokens if you ever might reach your max lease on tokens in the application you are using. However, rather than the database, I would suggest using Redis. If a token is lost, the app should be setup to continue running so it doesn't need to persist long term and Redis is much faster than using the database and there are some really simple Redis implementations out there too that should not take long to get up and running.

Dean Swiatek
  • 111
  • 2