Issue
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?
Solution
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.
Answered By - Daniel Bank