MSAL setActiveAccount from within function - reactjs

So I'm in the process of setting up MSAL with AAD for my react project. As it is, everything is working as expected, I can login, refresh the page (to get the latest active token) and logout.
However, as I was trying to refactor some of my code to make it more readable, I ran into a problem with setActiveState(), the following code works in my App.js:
export const msalInstance = new PublicClientApplication(msalConfig);
const accounts = msalInstance.getAllAccounts();
if (accounts.length > 0) {
msalInstance.setActiveAccount(accounts[0]);
}
However, I wanted to refactor the above as follows - To handle more cases in a joint function:
export const msalInstance = new PublicClientApplication(msalConfig);
function handleResponse(response) {
if (response !== null) {
const accountId = response.account.homeAccountId;
} else {
const currentAccounts = msalInstance.getAllAccounts();
if (currentAccounts.length === 0) {
// no accounts signed-in, attempt to sign a user in
instance.loginRedirect(loginRequest);
} else if (currentAccounts.length > 0) {
msalInstance.setActiveAccount(currentAccounts[0]);
}
}
}
msalInstance.handleRedirectPromise().then(handleResponse);
The above code is capable of getting all current accounts and it successfully reaches setActiveAccount, however nothing happens - I.e. my active account is not being set, which means that my <AuthenticationTemplate/> is stuck loading.

Please double-check your application, tenant, and client identifiers. Also, make sure that http://localhost:3000 is listed among the Allowed Web Origins in Azure's Application settings.
Check out this sample for further guidance.

Related

Authentication between two websites?

I am working on superset integration with kepler. so far, I have cloned the superset code and configured in local and for kepler I have created a react application with kepler plugin.
Now in Superset I have included a button named 'View in Kepler' to allow the user to navigate to my kepler application.
In Superset there is a login screen to validate the user but while redirecting how to perform the same?
Code that performing redirecting logic with json data(dashboard data) is attached below
const res = userListJson; // Proceed and get data from dataFromListOfUsersState function
if (typeof window !== 'undefined') {
if(check_time == true){
window.location.href = "http://localhost:3000/header?dashboard=time&filename="+res.data+"";
}else {
window.location.href = "http://localhost:3000/header?dashboard=header&filename="+res.data+"";
}
}
}).catch((error) => {
if (error.response_2) {
console.log(error.response_2)
console.log(error.response_2.status)
console.log(error.response_2.headers)
}
});

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

My dapp doesn't detect when an user change their Metamask Account

I am in the process of building a dapp for a project. I have one last thing to adjust: detect when a user changes metamask account to reset the state but it doesn't work.
//Doesn't work
window.ethereum.on('accountsChanged', function (accounts) {
console.log('accountsChanges', accounts);
setDefaultAccount(null);
});
// This works perfectly
window.ethereum.on('chainChanged', (chainId) => {
if(chainId !== "0x13881") {
setErrorMessage("Please connect on testnet Polygon Mumbai");
} else {
setErrorMessage(null);
window.location.reload();
}
});
I was also struggling with the same issue. Being unable to find the answer in the docs anywhere.
Until I'd realized that it's not meant to detect you switching from a connected account to a disconnected one.
In other words: it only detects it when you switch between accounts that are already connected to your Dapp. In that case - it functions perfectly. And detects an account change.
Go on ahead an test it on some popular Dapp out there. Connect just one of your accounts to it - then change it to another account on the same wallet, that is not yet connected - and it will also not be able to detect you changing it.
But if you connect two accounts right away - it will detect you switching between them and reflect your changes on its interface.
I tested this with PCS.
this is the correct way of implementation:
useEffect(() => {
ethereum?.on("accountsChanged", handleAccountChange);
return () => {
ethereum?.removeListener("accountsChanged", handleAccountChange);
};
});
Now write a listener for account change
const handleAccountChange = (...args) => {
// you can console to see the args
const accounts = args[0] ;
// if no accounts that means we are not connected
if (accounts.length === 0) {
console.log("Please connect to metamask");
// our old data is not current connected account
// currentAccount account that you already fetched and assume you stored it in useState
} else if (accounts[0] !== currentAccount) {
// if account changed you should update the currentAccount so you return the updated the data
// assuming you have [currentAccount,setCurrentAccount]=useState
// however you are tracking the state currentAccount, you have to update it. in case of redux you have to dispatch update action etc
setCurrentAccount(accounts[0)
}
};

Next JS - Handling getInitialProps on _app.js in SSR vs CSR

I am trying to create a Next JS application that handles the authentication and initial routing inside getInitialProps. I discovered this method can be executed either in the server or on the client.
My approach so far it's to have 2 different handlers based on detecting if I am in executing in the server checking for the presence of the req attribute inside of ctx.
This does the trick but doesn't feel like is the right way of doing. Can somebody, please, tell me if there is a cleaner way.
All authentication is handled in a separate subdomain, so I just need to redirect to the auth subdomain if there is no cookie or auth request fails for some other reason.
import "../../styles/globals.css";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
MyApp.getInitialProps = async (appContext) => {
let cookie, user;
let ctx = appContext.ctx;
//Check if I am in the server.
if (ctx.req) {
cookie = ctx.req.headers.cookie
//Do auth request.
//Redirect base on user properties
// handle redirects using res object
ctx.res.writeHead(302, { Location: "/crear-cuenta"});
} else {
cookie = window.document.cookie;
//Do auth request.
//Redirect base on user properties
//Do redirects using client side methods (useRouter hook, location.replace)???
}
//Return pageProps to the page with the authenticted user information.
return { pageProps: { user: user } };
};
export default MyApp;
I think your code is clean enough. Of course you still can maintain it.
My suggestion would be as the followings:
MyApp.getInitialProps = async (appContext) => {
in this line you can use object destructuring technique to get the context straightforward:
MyApp.getInitialProps = async ({ ctx }) => {
then you won't need this line for example anymore : let ctx = appContext.ctx;
The most important part of your code which can be cleaned up by the way is the area that you have written your auth request twice in an if/else condition. I would suggest you to implement that part like this:
const cookie = ctx.req ? ctx.req.headers.cookie : window.document.cookie;
Although I would try to keep everything in getInitialProps on server side, In that case I make a small change to get the cookie as following and process it in server-side only.
const cookie = cookie.parse(ctx.req ? ctx.req.headers.cookie || "" : undefined);
Note that: I'm using a cookie parser which u can install the package yourself as well. (npm install cookie)
if you need to do an extra check on your cookie at client side, I will do that in componentdidmount or in case you are using react hooks in useEffect. But it is not necessary.
Now you can implement //Do auth request once, which will cause cleaner code and of course to reduce unnecessary repetition.

Error: User credentials required in Google Cloud Print API

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.

Resources