Customizing the payload of a JWT token in a MEAN stack application - angularjs

In my web application, a user signs in and a token is created. Now i want to add a claim to the payload of this token: a boolean value for being an admin or not.
I've searched around the web, but i can't seem to find how to implement this in a Mean stack application.
My goal is when a user signs in, i can check if the user is an admin using the payload of the token. Then i can lock specific parts of my application so they are only accessible for admins.
This is the login function in the service
function logIn(user) {
return $http.post('/api/users/login', user, {
headers: {
Authorization: 'Bearer ' + getToken()
}
}).success(function(data) {
saveToken(data.token);
}).error(function(err){
return err;
});
}
These are the functions to save the token and get the token from local storage:
function saveToken(token) {
$window.localStorage['ptlab-app-token'] = token;
}
function getToken() {
return $window.localStorage['ptlab-app-token'];
}
And this is the function i use to check if a user is logged in in order to unlock specific parts of the application:
function isLoggedIn() {
var token = getToken();
if (token) {
var payload = angular.fromJson($window.atob(token.split('.')[1]));
return payload.exp > Date.now() / 1000;
} else {
return false;
}
}
So i want to do sort of the same as isLoggedIn with the admin check. But i can't seem to figure out how to customize the payload of the jwt token and add a claim to the payload. With a claim called "admin", i can easily check if the user is and admin without having to access the database.
Has anybody got any suggestions ?

You can adjust the payload for the token upon its creation (in Node.js), so you can simply add a isAdmin boolean. Afterwards you can decode the token and retrieve the value of isAdmin to see if the user is an admin or not.
Token creation:
const payload = {
id: user._id,
isAdmin: user.isAdmin,
};
const token = jwt.sign(payload, "superSecret", {
expiresIn: 86400,
});
Token decoding:
const decoded = await jwt.verify(token, "superSecret");
const isAdmin = decoded.isAdmin;

you may simply go to JWT.io decode your token and find the content and even you can send the isAdmin Key and value in token decode and verify it on client side in node it is jwt.decode and same in angular application.

Related

How to get Refresh Token for Spotify Web API?

I am working on a project using the Spotify Web JS API. I am able to successfully get the AccessToken, but I am coming across the issue of the token expiring and have found that I need to get a refresh token. Here is how I am currently getting the access token.
useEffect(() => {
const hash = window.location.hash;
let token = window.localStorage.getItem("token");
if (!token && hash) {
token = hash
.substring(1)
.split("&")
.find((elem) => elem.startsWith("access_token"))
.split("=")[1];
window.location.hash = "";
window.localStorage.setItem("token", token);
setToken(token);
}
spotify.setAccessToken(token);
spotify.getMyRecentlyPlayedTracks({ limit: 50 }).then((data) => {
setRecentlyPlayed(data.items);
});
spotify.getMySavedAlbums().then((user) => {
console.log("Saved albums:", user);
});
spotify.getFollowedArtists().then((user) => {
setNumberArtistsFollowing(user.artists.total);
});
spotify.getMyCurrentPlayingTrack().then((data) => {
if (data) {
setCurrentlyPlaying(data);
}
});
spotify.getMe().then(setUserProfile);
setToken(token);
}, []);
I have no experience with refresh tokens as this is my first project with tokens in general. Could anyone give me a hand? I am thinking that I need to request a refresh token in my code if the localstorage contains a token, do this on every page load regardless of whether the token is expired or not. I am unsure of how to get this refresh token though. Thank you!

Trying to use React-google-login just for accessing Google OAuth2 calendar API but giving errors - why?

I'm really new to OAuth2 so could really use some help. I have a site where users register and login via standard means. However, once they register, I want to connect their Google account so they can view/edit/modify their Google calendars. To this end, I installed react-google-login and have a component on the front-end that logs them into their account. That works fine (here's the code). Please note that the jsx is in styled components, which is why it has odd labels.
return (
<GoogleContainer>
<Logo src={GoogleLogo} />
<GoogleLogin
clientId = {process.env.REACT_APP_CLIENT_ID}
render={(renderProps) => (
<GoogleBtn
onClick={renderProps.onClick}
disabled={renderProps.disabled}
style={styleObj}
>
Connect to Google
</GoogleBtn>
)}
// buttonText='Sign in to Google Calendar'
onSuccess={responseGoogle}
isSignedIn={true}
onFailure={responseError}
cookiePolicy={"single_host_origin"}
responseType='code'
accessType='offline'
scope='openid email profile https://www.googleapis.com/auth/calendar '
/>{" "}
</GoogleContainer>
);
On the backend, I have code that grabs the refresh_token, stores it in a database and then I make a token object that I can send back to the frontend. Here is the code for that -
//This next fx will be used in the CreateTokens fx called by Google Login to identify user by the email captured in scope
const fetchInfo = async (accessToken) => {
const request = await axios.get(
`https://www.googleapis.com/oauth2/v2/userinfo?access_token=${accessToken}`
);
let response = await request;
let email = "";
if (response) {
email = response.data.email;
}
return email;
};
//Get authorization tokens from google calendar when signing into Google
const createTokens = async (req, res, next) => {
try {
const { code } = req.body;
const { tokens } = await oauth2Client.getToken(code);
accessToken = await tokens.access_token;
expiryDate = await tokens.expiry_date;
id_token = await tokens.id_token;
//Make an object with accessToken and expiry data and send to front end
const tokenObj = {
accessToken,
expiryDate,
id_token,
};
//Refresh Token goes to the database
const refreshToken = await tokens.refresh_token;
//We find user by using the scope variable from Google Login (frontend) - fx above
let email = await fetchInfo(accessToken);
if (refreshToken) {
//Parameters to update record by putting refreshToken in database
const filter = { email: email };
const update = { refreshToken: refreshToken };
let user = await User.findOneAndUpdate(filter, update, {
new: true,
});
}
res.send({ tokenObj });
} catch (error) {
next(error);
}
};
That also works fine as I get the refresh_token and store it in the database by user and the tokenObject with the access token gets sent back to the frontend. Here's where I'm confused and can use some help - first of all, I thought I needed to send the token to the frontend to store it but pretty much every time I refresh my page now, the frontend is sending a boatload of information to the console (with tons of information from Google - like the profile, tokens, etc). I don't know what code I wrote that is causing this or if it's a good thing or not. If it's automatically generated, do I even need to have backend code to get the token? Also, I'm getting another message that says " react_devtools_backend.js:3973 Your client application uses libraries for user authentication or authorization that will soon be deprecated. See the Migration Guide for more information." I thought this was up-to-date and not sure what part is deprecated. Ugh - sorry I'm so new to this and very confused. Any help would be much, much appreciated!!
Blockquote

read cookies in front (Reactjs) that comes from nodejs

i need to get cookies(it's a token) which has been defined in a node js Route file to my front, because i need to check infos of this token to show data if it's a user or admin.
THis is some code of the cookies :
// auth with google+
router.get('/auth/google', passport.authenticate('google', {
scope: [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email'
]
}));
// callback route for google to redirect to
// hand control to passport to use code to grab profile info
router.get('/auth/google/callback*', passport.authenticate('google'), (req, res) => {
if(req.user){
console.log(req.user);
res.cookie('token', req.user);
return res.redirect(config.clientURL);
}
else{
console.log('error');
return res.redirect(config.clientURL);
}
});
// auth with faceboook
router.get('/auth/facebook', passport.authenticate('facebook'));
// callback route for facebook to redirect to
// hand control to passport to use code to grab profile info
router.get('/auth/facebook/callback*', passport.authenticate('facebook'), (req, res) => {
console.log("je suis dans la route callback");
if(req.user){
console.log(req.user);
res.cookie('token', req.user);
return res.redirect(config.clientURL);
}
else{
console.log('error');
return res.redirect(config.clientURL);
}
});
Edit :
i did this :
const auth_head = document.cookie.split('.')[0];
const auth_payload = document.cookie.split('.')[1];
const auth_signature = document.cookie.split('.')[2];
var auth_token = auth_head + "." + auth_payload + "." + auth_signature;
console.log(JSON.parse( auth_head));
console.log(JSON.parse( auth_payload));
console.log(JSON.parse( auth_signature));
but i got this error :
Uncaught (in promise) SyntaxError: Unexpected token o in JSON at position 1
Thank you
As I mentioned in the comments, it's good advice to use httpOnly flag when setting cookies; this means that you need another strategy to return the user data.
Option 1: One easier to implement way could be: After your server redirects the client to let's say /logged-in, you can fetch the user data from let's say /api/userinfo; the response should a json containing the user info; you should use that json to store the information in your client using localStorate.setItem(...). This is the classic and more used way to store your user data in the client.
Example Server (Create an endpoint that returns the logged-in user info):
// Server endpoint that returns user info
router.get('/api/userinfo',
passport.authenticate(your_strategy_here),
(req, res) => {
res.json({ name: req.user.name, role: req.user.role }); // Return just what you need
})
Example Client (Create a component that requests the user info from the new server endpoint):
componentDidMount(){
fetch('/api/userinfo')
.then( res => res.json() )
.then( user => localStorate.setItem('user', user);
}
Option 2: Give Google a URL which is resolved by the client, and then have the client send the request to /auth/facebook/callback; then have the server do res.json(user), instead of the doing the redirect.
Google -> /your-client-app/auth/callback
Client -> /auth/facebook/callback
Option 2 is my advice, however, Option 1 may be more straight forward for your current setup.
Option 3: Disable httpOnly when setting the cookie, there are security concerns when doing this and it's not meant to be done like that in production apps.
res.cookie('token', req.user, { httpOnly: false });
And then on your client, you can use the following data to check the cookies.
const cookieData = document.cookie;
console.log(cookieData)

redux-api sync action inside a prefetch block

I am coding a SPA in react.js and I am using redux-api to handle backend connection. I want to do a sync action to refresh the auth token before doing the main action; this way, every time I will do an action to the backend I will be sure that the token is valid.
const endpoints = {
{
url: '/some/url',
crud:true,
prefetch:[
({actions, dispatch, getState}, cb) =>{
actions.auth_token.post(JSON.stringify({
token: "my token",
refreshToken: "my_refresh_token"
}),null, (err, data) =>{
if(err){
// HANDLE ERROR
}
setToken(data)
})
}
]
}
}
const api = reduxApi(endpoints)
How can I call the prefetch function in a sync way? So first the token refreshes and then the Action?
EDIT
We can do the stuff async, the important is the final call to cb(), here is the example
const endpoints = {
{
url: '/some/url',
crud:true,
prefetch:[
({actions, dispatch, getState}, cb) =>{
let mills = new Date().getTime()
const { token, generationTime, accessTokenLife, refreshTokenLife, refreshToken } = localStorage
// Conditions: exixts token, it is expired, refresh token is not expired
if(token && generationTime + accessTokenLife - 500 < mills && generationTime + refreshTokenLife - 500 > mills){
dispatch(actions.token_refresh.get(null, null, (err, data) =>{
if(err){
dispatch(setError(err))
}else{
refreshTokenData(data)
}
cb()
}))
}else{
cb()
}
}
]}}
const api = reduxApi(endpoints)
You may not need to request the token every time you do an async action. In fact, I'd encourage you not to.
You can request the token when you authenticate the user and cache it using web storage. Now instead of sending a network request to retrieve the users token every time you need it, you simply check the browsers cached storage. If the token for the user exists then the user has successfully authenticated. Otherwise, the user has not logged in and you can redirect the user to the authentication page.
Since that was not actually an answer to your problem but rather a different way to solve your problem I will also answer your question in a way that is more inline with the question. You should be able to utilize promise chaining to request the user's token and then once that resolves, do any other action.
I will explain in an abstract way that is not explicity related to redux-api that you should be able to adapt to redux-api specific constructs easy enough.
const actionOne = () => {
actions.post(myJson)
.then(response => actionTwo(response))
.catch(error => console.log(error))
}
An important modification you would need to make is to convert actions.auth_token.post to return a promise. Then you can chain other actions to the resolution of that promise. If you are not familiar with promises MDNs documentation is quite good. For more information on converting a function from callbacks to promises this Stack Overflow answer is quite detailed.

Using passport-facebook without Mongoose User (No Mongo in the MEAN stack)

I'm very new to the MEAN stack, and this might seem to be very naive or wrong approach, but I want to ask that when we authenticate using passport-facebook strategy, using the following code:
var FacebookStrategy = require('passport-facebook').Strategy;
var User = require('../models/user');
var fbConfig = require('../fb.js');
module.exports = function(passport) {
passport.use('facebook', new FacebookStrategy({
clientID : fbConfig.appID,
clientSecret : fbConfig.appSecret,
callbackURL : fbConfig.callbackUrl
},
// facebook will send back the tokens and profile
function(access_token, refresh_token, profile, done) {
console.log('profile', profile);
// asynchronous
process.nextTick(function() {
// find the user in the database based on their facebook id
User.findOne({ 'id' : profile.id }, function(err, user) {
// if there is an error, stop everything and return that
// ie an error connecting to the database
if (err)
return done(err);
// if the user is found, then log them in
if (user) {
return done(null, user); // user found, return that user
} else {
// if there is no user found with that facebook id, create them
var newUser = new User();
// set all of the facebook information in our user model
newUser.fb.id = profile.id; // set the users facebook id
newUser.fb.access_token = access_token; // we will save the token that facebook provides to the user
newUser.fb.firstName = profile.name.givenName;
newUser.fb.lastName = profile.name.familyName; // look at the passport user profile to see how names are returned
//newUser.fb.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first
// save our user to the database
newUser.save(function(err) {
if (err)
throw err;
// if successful, return the new user
return done(null, newUser);
});
}
});
});
}));
};
I don't need to store the user information in any data store. I want to store the token only for the time the user is logged into my web application, basically I don't have the need to use Mongo, because all the data that will be displayed in the web application will come from Facebook api, for example the posts for a profile, the number of likes on a particular posts etc. I don't need to have a backend as such, because if I store the data in any data store such as Mongo, the next time the user login then the data will be stale (in a way the Facebook api is kind of my backend), and I also want that the updates for information on any posts done on Facebook should be updated realtime on my web application for e.g. if someone likes a post on the actual Facebook page the number of likes on my web application should also be updated in realtime, so it seems unnecessary to first bring the data from the Facebook SDK and then store it in Mongo, why not just give it to the controller and from there the view can present the data. If my approach is wrong please do correct me.
So basically every time the user logs in an access token is created and used for that session, when the user logs out the access token is destroyed and so completely eliminates the need for storing the token and any data that is brought in using the Facebook SDK.
Replace the function call
User.findOne({ 'id' : profile.id }, function(err, user) {
With facebook sdk authentication call and return the user object when it's validated.
return done(null, user);
Please refer...
https://github.com/jaredhanson/passport-facebook
you need to create a new user template in the model folder. I have created the following: user.js
var facebook = module.exports.facebook = {
id : String,
token : String,
email : String,
name : String
}
and then change the passport.serializeUser and passport.deserializeUser functions.
passport.serializeUser(function(user, done) {
done(null, user.facebook.id);
});
// used to deserialize the user
//passport.deserializeUser(function(id, done) {
passport.deserializeUser(function(id, done) {
done(null, { id: User.facebook.id, token: User.facebook.token, name: User.facebook.name, email: User.facebook.email})
});
then the function: process.nextTick(function() {} replace the content by this code :
var newUser = User;
// set all of the facebook information in our user model
newUser.facebook.id = profile.id; // set the users facebook id
newUser.facebook.token = token; // we will save the token that facebook provides to the user
newUser.facebook.name = profile.name.givenName + ' ' + profile.name.familyName; // look at the passport user profile to see how names are returned
newUser.facebook.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first
return done(null, newUser);
add the line profileFields: ['id', 'displayName', 'photos', 'emails', 'name'] in function passport.use(new FacebookStrategy({}
change the profile.ejs file by removing the local information div and changing the properties <% = user.facebook.id%> to <% = user.id%> and so on in the others.

Resources