How should a Facebook user access token be consumed on the server-side?

20,488

Solution 1

From what you describe I'd suggest to use a server-side login flow as described in

so that the token is already on your server, and doesn't need to be passed from the client. If you're using non-encrypted connections, this could be a security risk (e.g. for man-in-the-middle attacks).

The steps would be:

(1) Logging people in

You need to specify the permission you want to gather from the users in the scope parameter. The request can be triggered just via a normal link:

GET https://www.facebook.com/dialog/oauth?
    client_id={app-id}
   &redirect_uri={redirect-uri}
   &response_type=code
   &scope={permission_list}

See

(2) Confirm the identitity

GET https://graph.facebook.com/oauth/access_token?
    client_id={app-id}
   &redirect_uri={redirect-uri}
   &client_secret={app-secret}
   &code={code-parameter}

(3) Inspect the access token

You can inspect the token as you already said in your question via

GET /debug_token?input_token={token-to-inspect}
    &access_token={app-token-or-admin-token}

This should only be done server-side, because otherwise you'd make you app access token visible to end users (not a good idea!).

See

(4) Extending the access token

Once you got the (short-lived) token, you can do a call to extend the token as described in

like the following:

GET /oauth/access_token?grant_type=fb_exchange_token
    &client_id={app-id}
    &client_secret={app-secret}
    &fb_exchange_token={short-lived-token}

(5) Storing of access tokens

Concerning the storing of the tokens on the server, FB suggests to do so:

(6) Handling expired access tokens

As FB doesn't notify you if a token has expired (and if you don't save the expiry date and compare this to the current timestamp before making a call), it's possible that you receive error messages from FB if the token got invalid (after max. 60 days). The error code will be 190:

{
  "error": {
    "message": "Error validating access token: Session has expired at unix 
                time SOME_TIME. The current unix time is SOME_TIME.", 
    "type": "OAuthException", 
    "code": 190
  }
}

See

If the access token becomes invalid, the solution is to have the person log in again, at which point you will be able to make API calls on their behalf once more. The login flow your app uses for new people should determine which method you need to adopt.

Solution 2

  1. I dont' see any glaring gaps / pit falls, but I'm not a security expert.
  2. Once your server has verified the given token (step 8), as you said:

The accepted answer in this StackOverflow question recommends creating a custom access token after the first verification of the Facebook user token is complete. The custom token would then be sent to the client for subsequent requests. I’m wondering if this is more complex than the above solution, however. This would require implementing my own Identity Provider (something I want to avoid because I want to use external identity providers in the first place…). Is there any merit to this suggestion?

IMHO is the way to go. I would use https://jwt.io/ which allows you to encode values (the userId for example) using a secret key. Then your client attach this token to every request. So you can verify the request without need to a third party (you don't need database queries neither). The nice thing here is there is no need to store the token on your DB.

You can define an expiration date on the token, to force the client authenticate with the third party again when you want.

  1. Let's say you want your server be able to do some action without the client interaction. For example: Open graph stories. In this scenario because you need to publish something in the name of the user you would need the access token stored on your DB.

(I can not help with the 3 and 4 questions, sorry).

Solution 3

Problem with Facebook is that they do not use OpenId connect on top of Oauth (https://blog.runscope.com/posts/understanding-oauth-2-and-openid-connect).
Thus resulting in their custom ways of providing Oauth authentification.

Oauth2 with OpenId connect identity services usually provide issuer endpoint where you can find URL (by appending ".well-known/openid-configuration") for jwk's which can be used to verify that JWT token and its contents were signed by the same identity service. (i.e access token originated from the same service that provided you jwk's)

For example some known openid connect identity providers:
https://accounts.google.com/.well-known/openid-configuration
https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
(btw it is not a coincidence that Attlasian provides only these two services to perform external login)

Now as you mentioned, you need to support multiple oauth providers and since like Facebook not all providers use same configuration of oauth (they use different JWT attribute names, toke verification methods, etc. (Openid connect tries to unify this process)) i would suggest you to use some middleware identity provider like Oauth0 (service not protocol) or Keycloak. These can be used with external identity providers (Social pages as you mentioned) and also provides you with custom user store.

Advantage is that they unify authentication process under one type (e.g both support openid connect). Whereas when using multiple oauth providers with not unified authentication workflow you will end up with redudant implementations and need for merging different information's under one type (this is basically what mentioned middle-ware identity providers solve for you).

So if you will use only Facebook as identity provider in your app then go for it and make implementation directly for Facebook Oauth workflow. But with multiple identity providers (which is almost always case when creating public services) you should stick with mentioned workaround or find another one (or maybe wait till all social services will support Openid connect, which they probably wont).

Share:
20,488
Scott Lin
Author by

Scott Lin

Updated on July 05, 2022

Comments

  • Scott Lin
    Scott Lin almost 2 years

    Preface

    I'm developing several web services and a handful of clients (web app, mobile, etc.) which will interface with said services over HTTP(s). My current work item is to design an authentication and authorization solution for the product. I have decided to leverage external identity providers, such as Facebook, Google, Microsoft, Twitter, and the like for authentication.

    I'm trying to solve the problem of, "when a request comes to my server, how do I know who the user is and how can I be sure?". More questions below as well...

    Requirements

    1. Rely on external identities to indicate who I'm dealing with ('userId' essentially is all I care about).
    2. The system should use token-based authentication (as opposed to cookies for example or basic auth).

      I believe this is the right choice for scaling across multiple clients and servers while providing loose coupling.

    Workflow

    Based on my reading and understanding of token-based authentication, the following is how I imagine the workflow to be. Let's focus for now on Facebook in a web browser. My assumption is that other external identity providers should have similar capabilities, though I have not confirmed just yet.

    Note, as of writing, I'm basing the following off of Facebook login version 2.2

    1. Client: Initiates login to Facebook using the JavaScript SDK
    2. Facebook: User authenticates and approves app permissions (to access user's public profile for example)
    3. Facebook: Sends response to client which contains user’s access token, ID, and signed request
    4. Client: Stores user access token in browser session (handled by SDK conveniently)
    5. Client: Makes a request to my web service for a secure resource by sending along the user’s access token in the authorization header + the user’s ID (in custom header potentially)
    6. Server: Reads user access token from request header and initiates verification by sending a request to the debug_token graph API provided by Facebook
    7. Facebook: Responds back to the server with the user access token info (contains appId and userId)
    8. Server: Completes verification of the token by comparing the appId to what is expected (known to itself) and the userId to what was sent on the client request
    9. Server: Responds to the client with the requested resource (assuming the happy authorization path)

    I’m imagining steps 5-9 would be repeated for subsequent requests to the server (while the user’s access token is valid – not expired, revoked from FB side, app permissions changed, etc.)

    Here's a diagram to help go along with the steps. Please understand this system is not a single page application (SPA). The web services mentioned are API endpoints serving JSON data back to clients essentially; they are not serving HTML/JS/CSS (with the exception of the web client servers).

    Workflow diagram

    Questions

    1. First and foremost, are there any glaring gaps / pit falls with the described approach based on my preface and requirements?

    2. Is performing an outbound request to Facebook for verifying the access token (steps 6-8 above) per client request required / recommended?

      I know at the very least, I must verify the access token coming from the client request. However, the recommended approach for subsequent verifications after the first is unknown to me. If there are typical patterns, I’m interested in hearing about them. I understand they may be application dependent based on my requirements; however, I just don’t know what to look for yet. I’ll put in the due diligence once I have a basic idea.

      For instance, possible thoughts:

      • Hash the access token + userId pair after first verification is complete and store it in a distributed cache (accessible by all web servers) with expiry equal to access tokens. Upon subsequent requests from the clients, hash the access token + userId pair and check its existence in the cache. If present, then request is authorized. Otherwise, reach out to Facebook graph API to confirm the access token. I’m assuming this strategy might be feasible if I’m using HTTPS (which I will be). However, how does performance compare?

      • The accepted answer in this StackOverflow question recommends creating a custom access token after the first verification of the Facebook user token is complete. The custom token would then be sent to the client for subsequent requests. I’m wondering if this is more complex than the above solution, however. This would require implementing my own Identity Provider (something I want to avoid because I want to use external identity providers in the first place…). Is there any merit to this suggestion?

    3. Is the signedRequest field present on the response in step #3 above (mentioned here), equivalent to the signed request parameter here in the ‘games canvas login’ flow?

      They seem to be hinted as equivalent since the former links to the latter in the documentation. However, I’m surprised the verification strategy mentioned on the games page isn’t mentioned in the ‘manually building a login flow’ page of the web documentation.

    4. If the answer to #3 is ‘Yes’, can the same identity confirmation strategy of decoding the signature and comparing to what is expected to be used on the server-side?

      Decode & compare from FB docs

      I’m wondering if this can be leveraged instead of making an outbound call to the debug_token graph API (step #6 above) to confirm the access token as recommended here:

      debug_token graph API from FB docs

      Of course, in order to make the comparison on the server-side, the signed request portion would need to be sent along with the request to the server (step #5 above). In addition to feasibility without sacrificing security, I’m wondering how the performance would compare to making the outbound call.

    5. While I’m at it, in what scenario / for what purpose, would you persist a user's access token to a database for example? I don’t see a scenario where I would need to do this, however, I may be overlooking something. I’m curious was some common scenarios might be to spark some thoughts.

    Thanks!

  • Scott Lin
    Scott Lin over 9 years
    Thanks for taking the time to respond, Tobi. Excuse my brevity, but comments are limited. My thoughts after reading: 1. Several of the specific questions are not addressed. 2. How does your suggested code flow work for authenticating client requests to web services? Could you expound upon the client-server interaction? As your answer stands now, nothing in it talks about the client. Perhaps it was unclear from my original question, but I feel you've misunderstood the role of the "server" entity in the workflow steps. I've edited the question to clarify. Apologies for any confusion here.
  • jayant singh
    jayant singh about 7 years
    access_token={app-token-or-admin-token} i am using app id for this and it is showing "message": "Invalid OAuth access token.", for Inspecting the access token ?
  • Duncan Jones
    Duncan Jones about 6 years
    I put a bounty on this question, in the hope that someone would fill in some of the gaps in your answer. But they didn't, and yours is still the most comprehensive, so have some extra points!
  • Tobi
    Tobi about 6 years
    Thanks @DuncanJones What were your remaining questions? Maybe I can help...
  • Ari
    Ari over 4 years
    Do you recommend to store the user info (eg. name, email, phone) on the database for the next login?