How to send oauth2 tokens to client and store them?

Viewed 3300

I have an app where I want to sign in with a 3rd party API (such as Google, Facebook, etc.). The client side front end is just a JavaScript SPA that will interact with my server. The server essentially is only needed to store the 3rd party ClientID and ClientSecret.

To login, the client will link the user to MyAPI/login. MyAPI will then redirect the user to the 3rd party login page, along with the ClientID. Upon authentication, the 3rd party will redirect the user back to MyAPI/callback with a code query parameter. MyAPI will send this code back to the 3rd party API along with ClientID and ClientSecret. The 3rd party API will finally return an access_token and refresh_token.

My question is how I should then send the tokens back to the client application? And, once the client has the tokens, how should I store them?

enter image description here

1 Answers

What you are describing is the Authorization Code Grant flow of OAuth. In Auth Code flow, a code is provided to the client (in this case, MyAPI) so that the user never receives the access_token or refresh_token and thus doesn't have to be trusted with them.

By providing these tokens to the user and allowing them to store it in local storage, you are circumventing the security benefit of Auth Code flow. It is definitely not recommended to do this unless security isn't as big of an issue for you (you trust the user).

As the comments suggest, you can use cookies to persist a session with MyAPI. Still, the access_token and refresh_token should be kept in the session data and not shared directly with the user. This enforces that access to the 3rd party API is only made by MyAPI on behalf of the user.

A better explanation of Auth Code flow is provided in the accepted answer to this question: What is the difference between the 2 workflows? When to use Authorization Code flow?

An incomplete Express.js example of MyAPI:

// imports
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const jwt = require('jsonwebtoken');

// setup passport (an authentication middleware) and use it with the session provided by express-session
passport.serializeUser((user, done) => {
  done(null, user);
});
passport.deserializeUser((obj, done) => {
  done(null, obj);
});
passport.use(new SomeStrategy(...strategyOptions));
const app = express();
app.use(passport.initialize());
app.use(passport.session());

// route handlers
app.get('/login', passport.authenticate('SOME_STRATEGY'), () => {});
app.get('/callback', passport.authenticate('SOME_STRATEGY'), { failureRedirect: '/badlogin' }, (req, res) => res.send(200));
app.get('/resource', (req, res) => {
  const accessToken = req.user.access_token; // req.user has the session information including the access token
  try {
    // verify the access token with the 3rd party auth's public key (NOT THE SAME AS DECODING IT!) 
    const decodedAccessToken = jwt.verify(accessToken, thirdPartAuthPublicKey);
    return res.send(200).send(decodedAccessToken);
  } catch (err) {
    return res.send(401).send('unauthenticated');
  }
});

Here, the server keeps the access_token in the user's session data (req.user). When the user makes a request for the resource, the server tries to verify the access_token and returns its decoded contents.

Related