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!
Related
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
What is the correct way to request a new JWT authentication token via refresh tokens within AngularJS?
I already have an implementation that, on every API request, checks whether the session needs refreshing and, if so, requests a new token from the webserver. But, if a page makes 3 calls at once it requests a new refresh token for each call which seems incorrect - I would think it should only update the token once.
Is there a way to block other calls or should I put the refresh on an interval and not do it via interceptors?
Interceptor request method
request = (config: angular.IRequestConfig) => {
this.setBearerToken(config);
var responsePromise = this.$q.when(config);
if (config.url !== this.tokenUrl) {
const factSession = this.$injector.get("factSession") as Session;
if (factSession.shouldRefreshToken()) {
responsePromise = factSession
.refreshSession()
.then(() => {
this.setBearerToken(config);
return config;
});
}
}
return responsePromise;
}
Just enhance your refreshSession method, so it wont refresh several times.
var refreshPromise;
refreshSession: () => {
if (!refreshPromise) {
refreshPromise = $http.get(...);
refreshPromise.finally(() => refreshPromise = null);
}
return refreshPromise;
}
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.
I'm trying to use Google Cloud Print(GCP) API, but I can't make it works.
Maybe I've understood bad the workflow because is the first time I'm using the google api, please help me to understand how to make it works.
Initial considerations:
I'm trying to implement it in reactJS, but It is indifferent because the logic to make GCP works is independent of the technology. Then you also can help me understand the workflow.
What exactly I want:
To make my first test, I am looking to get all information about my printer.
What I did:
I created a project in: https://console.developers.google.com
Inside the project created, I created a credential:
create credentials -> OAuth client ID
And I chose Application type: Web, and also configure the restrictions to source and redirection to my localhost.
Manually in https://www.google.com/cloudprint, I added my printer, I made a test printing a PDF and was OK.
I created a project in reactJS to get the information of my printer I've added.
Component:
Explanation:
I'm using a component react-google-login to obtain easily the user accessToken: https://github.com/anthonyjgrove/react-google-login
This component only obtains the access token and save it in localStorage, in a variable called googleToken and it draws a button to call a function to obtain the information about the printer.
code:
import React, { Component } from 'react'
import GoogleLogin from 'react-google-login';
import { connect } from 'react-redux'
import { getPrinters } from '../actions/settings'
class Setting extends Component {
responseGoogle(response) {
const accessToken = response.accessToken
localStorage.setItem('googleToken', accessToken)
}
render() {
return (
<div>
<GoogleLogin
clientId="CLIENT_ID_REMOVED_INTENTIONALLY.apps.googleusercontent.com"
buttonText="Login"
onSuccess={this.responseGoogle}
onFailure={this.responseGoogle}
/>
<button
onClick = {() => {
this.props.getPrinters()
}}
>test printer</button>
</div>
)
}
}
const mapStateToProps = state => {
return {
state: state
}
}
const mapDispatchToProps = dispatch => {
return {
getPrinters() {
dispatch(getPrinters())
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Setting)
Action or Function to get information printer:
Explanation:
I'm passing the parameter printerid to get information about that printer.
In authorization, I'm using OAuth ... because in the documentation says that(second paragraph).: https://developers.google.com/cloud-print/docs/appInterfaces
The next two headers I wrote it because I tried solutions as:
Google Cloud Print API: User credentials required
Google Cloud Print User credentials required
code:
import axios from 'axios'
axios.defaults.headers.common['Authorization'] = 'OAuth ' + localStorage.getItem('googleToken')
axios.defaults.headers.common['scope'] = 'https://www.googleapis.com/auth/cloudprint'
axios.defaults.headers.common['X-CloudPrint-Proxy'] = 'printingTest'
const getPrinters = () => {
return () => {
return axios.get('https://www.google.com/cloudprint/printer'
, {
params: {
printeid: 'PRINTER_ID_REMOVED_INTENTIONALLY'
}
}
)
.then(response => {
console.log('response of google cloud print')
console.log(response)
})
}
}
export { getPrinters }
Error:
After all explained before, I got the next error:
User credentials required
Error 403
Note:
I'm using CORS plugin by recommendation of:
Chrome extensions for silent print?
because initially, I had cors error.
Any suggestion or recommendation would be very useful, thanks.
I've resolved my problem, my main problem about User Credential required were because I was using the incorrect access token and It was because I was getting the access token incorrectly.
I'm going to explain my whole solution because there are few examples of codes with this API.
Solutions:
The steps described were Ok until the fourth step where I used the external component react-google-login to trying to get the access token, instead I used googleapis module: Link Github googleapis
Also to avoid CORS problem(and not use CORS chrome plugin) I wrote the requests to Google API in server side.(NODEJS)
I had also a problem in the frontend when I tried to generate a popup to give permission for printer(problems about CORS), my solution was to use this very simple module for authentication: Link Github oauth-open
General scheme:
Explanation:
Knowing I have all data described in my question post(until the third step).
Authentication:
The next step in getting a URL and use it to the user can authenticate.
As I said before I used the module oauth-open in the frontend to generate the popup and only this module need the URL. To get the URL in the backend I used the endpoint /googleurl, where here I used the method generateAuthUrl of the module googleapis to generate the URL.
After that In the frontend, I got the authentication_code(that returned the module oauth-open), I send It to my endpoint /googletoken and here I process the authentication_code to generate access token, refresh token and expiration date with the method getToken of the module googleapis. Finally, these data are stored in the database.
Print:
For print, since the frontend, I send what data I need send to the printer. I used my endpoint /print
In the backend endpoint, my logic was the next:
Recover tokens and expiration date from database, with the expiration date check if the token has expired, and if It has already expired then gets another token and replace the old access token with the new one, replacing also with the new expiration date, to obtain this new data only is necessary call to method refreshAccessToken of module googleapis.Note: the refresh token never expires.
After having the access token updated, use it to send data to the printer with Google route(.../submit)
Code:
All the next codes are in only 1 file
Some data as validation, static variables, error handler, etc, has been removed to better understanding.
Route get URL authentication.
const express = require('express');
const google = require('googleapis');
const router = express.Router();
var OAuth2 = google.auth.OAuth2;
const redirect_url = 'http://localhost:3001/setting'; //Your redirect URL
var oauth2Client = new OAuth2(
'CLIENT ID', //Replace it with your client id
'CLIEND SECRET', //Replace it with your client secret
redirect_url
);
var url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/cloudprint'
});
router.get('/googleurl', (req, res) => {
return res.status(200).send({
result: { googleURLToken: url }
});
});
To get tokens using the authentication code and save these in the database.
const Setting = require('../models/setting'); // My model(Mongoose)
router.post('/googletoken', (req, res) => {
oauth2Client.getToken(req.body.code, function (err, tokens) {
oauth2Client.credentials = tokens;
// If refresh token exits save it
// because the refresh token it returned only 1 time! IMPORTANT
if (tokens.hasOwnProperty('refresh_token')) {
let setting = new Setting();
setting.refreshTokenGoogle = tokens.refresh_token;
setting.expirationTokenGoogle = tokens.expiry_date;
setting.tokenGoogle = tokens.access_token;
setting.save()
.then((settingCreated) => {
return res.status(200).send({
message: 'OK'
});
})
}
});
});
To print
const axios = require('axios');
const moment = require('moment');
router.post('/print',async (req, res) => {
const tickeProperties = {
'version': '1.0',
'print': {
'vendor_ticket_item': [],
'color': { 'type': 'STANDARD_MONOCHROME' },
'copies': { 'copies': 1 }
}
};
const accessToken = await getTokenGoogleUpdated();
axios.get(
'https://www.google.com/cloudprint/submit',
{
params: {
printerid : printerID, // Replace by your printer ID
title: 'title printer',
ticket: tickeProperties,
content : 'print this text of example!!!',
contentType: 'text/plain'
},
headers: {
'Authorization': 'Bearer ' + accessToken
}
}
)
.then(response => {
return res.status(200).send({
result: response.data
});
})
}
);
async function getTokenGoogleUpdated() {
return await Setting.find({})
.then(async setting => {
const refreshTokenGoogle = setting[0].refreshTokenGoogle;
const expirationTokenGoogle = setting[0].expirationTokenGoogle;
const tokenGoogle = setting[0].tokenGoogle;
const dateToday = new Date();
// 1 minute forward to avoid exact time
const dateTodayPlus1Minute = moment(dateToday).add(1, 'm').toDate();
const dateExpiration = new Date(expirationTokenGoogle);
// Case date expiration, get new token
if (dateExpiration < dateTodayPlus1Minute) {
console.log('Updating access token');
oauth2Client.credentials['refresh_token'] = refreshTokenGoogle;
return await oauth2Client.refreshAccessToken( async function(err, tokens) {
// Save new token and new expiration
setting[0].expirationTokenGoogle = tokens.expiry_date;
setting[0].tokenGoogle = tokens.access_token;
await setting[0].save();
return tokens.access_token;
});
} else {
console.log('Using old access token');
return tokenGoogle;
}
})
.catch(err => {
console.log(err);
});
}
I hope It helps you if you want to use Google Cloud Print to not waste a lot of time as I did.
The important part there is a scope https://www.googleapis.com/auth/cloudprint which is not obvious and took one day for me to figure out.
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.