msal.js cannot get the refresh_token even though offline_access is available and I can see it in the return call - microsoft-graph-api

I use msal.js to get access to the Microsoft Graph Api and I have gotten it working for the post part.
As you can see on the images I get the refresh_token in the response payload but not in the actual output when I console log it, how do I do this ?
Then thing is I need this refresh_token as the the place they auth their microsoft account is not the same place as the data is actually shown.
Let me explain
We are a infoscreen company and we need this to show the calendar of the people who authed when they have inserted it on the presentation.
so the flow is as follows:
They install the app and login with their Microsoft 365 account to give us access to this data. (this is the part that returns the refresh token and access Token).
They go to the presentation and insert the app in the area they want to show it.
on the actual monitor that could stand anywhere in the world the calendar would now show up.
But after 1 hour the session would expire so we need to generate a new access_token and for this we need the refresh_token.
At the step of loginPopup I can see there is a refreshToken
but when I use the data it is gone, I also tried to request token silently
Also I updated to the newest version of msal-browser.min.js version 2.1 that should support it.
async function signInWithMicrosoft(){
$(".notification_box", document).hide();
$("#table_main", document).show();
const msalConfig = {
auth: {
clientId: '{CLIENTID}',
redirectUri: '{REDIRECTURI}',
validateAuthority: false
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
forceRefresh: false
},
};
const loginRequest = {
scopes: [
"offline_access",
"User.Read",
"Calendars.Read",
"Calendars.Read.shared"
],
prompt: 'select_account'
}
try {
const msalClient = new msal.PublicClientApplication(msalConfig);
const msalClientLoggedIn= await msalClient.loginPopup(loginRequest).then((tokenResponse) => { console.log(tokenResponse); });
msalClientAccounts = msalClient.getAllAccounts();
var msalInsertAccount = true;
var tableMainAsText = $("#table_main", document).text();
if(typeof msalClientLoggedIn.idTokenClaims !== 'undefined'){
if(tableMainAsText.indexOf(msalClientLoggedIn.idTokenClaims.preferred_username)>-1){
msalInsertAccount = false;
}
if(msalInsertAccount){
var tableRow = "<tr>"+
"<td>"+msalClientLoggedIn.idTokenClaims.name+" ("+msalClientLoggedIn.idTokenClaims.preferred_username+") <span style='display: none;'>"+msalClientAccounts[0].username+"</span><input type=\"hidden\" name=\"app_config[exchange_online][]\" class=\"exchange_online_authed_account\" value=\""+msalClientLoggedIn.idTokenClaims.preferred_username+","+msalClientLoggedIn.idTokenClaims.name+"\" /></td>"+
"<td class=\"last\">Fjern adgang</td>"+
"</tr>";
$("#table_body", document).append(tableRow);
$("#table_foot", document).hide();
}
}
}catch(error){
$(".notification_box", document).show();
}
}

Related

Getting List of YouTube Channel Subscriptions using YouTube Data API V3 in JavaScript

I have been trying to get the list of subscriptions of my channel but unfortunately I get errors every time I run my code, I am describing each step below:
Step 1: I created this channel: My YouTube Channel
Step 2: I enabled the YouTube Data API V3 in Google Developer Console
Step 3: I created API Key and Google OAuth 2.0 Client ID, you can see the following screenshot:
Step 4: I checked the YouTube API Reference and checked some parameters here and got a successful response with all the subscriptions of my channel: YouTube API Reference for my Channel
Step 5: I copied the following code from the YouTube API Reference and placed my own API Key and Google OAuth 2.0 Client ID after I got a successful response for my channel:
<script src="https://apis.google.com/js/api.js"></script>
<script>
/**
* Sample JavaScript code for youtube.subscriptions.list
* See instructions for running APIs Explorer code samples locally:
* https://developers.google.com/explorer-help/code-samples#javascript
*/
function authenticate() {
return gapi.auth2.getAuthInstance()
.signIn({scope: "https://www.googleapis.com/auth/youtube.readonly"})
.then(function() { console.log("Sign-in successful"); },
function(err) { console.error("Error signing in", err); });
}
function loadClient() {
gapi.client.setApiKey("YOUR_API_KEY");
return gapi.client.load("https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest")
.then(function() { console.log("GAPI client loaded for API"); },
function(err) { console.error("Error loading GAPI client for API", err); });
}
// Make sure the client is loaded and sign-in is complete before calling this method.
function execute() {
return gapi.client.youtube.subscriptions.list({
"part": [
"subscriberSnippet,contentDetails"
],
"channelId": "UCLlE_JEV7I0pQ7fhY4BIrrQ"
})
.then(function(response) {
// Handle the results here (response.result has the parsed body).
console.log("Response", response);
},
function(err) { console.error("Execute error", err); });
}
gapi.load("client:auth2", function() {
gapi.auth2.init({client_id: "YOUR_CLIENT_ID"});
});
Step 6: Then I added the following buttons in my HTML code:
<button onclick="authenticate().then(loadClient)">authorize and load</button>
<button onclick="execute()">execute</button>
Step 7: When I execute the code, I get the following error messages:
Error 1:
"You have created a new client application that uses libraries for user authentication or
authorization that will soon be deprecated. New clients must use the new libraries instead;
existing clients must also migrate before these libraries are deprecated. See the [Migration
Guide](https://developers.google.com/identity/gsi/web/guides/gis-migration) for more
information."
Step 8: Then I click the authorize and load button and sign in to my channel and allow any requested rights. After that, when I click the execute button, then I get the following error:
"The requester is not allowed to access the requested subscriptions."
This is worth mentioning that this is my own channel, I login when required and I allow any rights that are requested. I also use my own Google Developer Console account, my own API Key and my own OAuth 2.0 Client ID. I have enabled the YouTube Data API V3 and I have set up everything properly. I can get the proper result from the YouTube API reference but I can't get it using JS.
Any help is appreciated in advance.
error one.
You have created a new client application that uses libraries for user authentication or
authorization that will soon be deprecated. New clients must use the new libraries instead;
existing clients must also migrate before these libraries are deprecated. See the Migration
Guide for more
information.
The code you are using is using the old google sign-in. You need to change this and use the new Google authorization library Authorizing for Web
Error two:
The requester is not allowed to access the requested subscriptions.
This error is a little harder to understand. First off when the authorization request pops up it should be asking you to pick a user and then a channel Make sure you select the channel that maps to the channel id you are selecting UCLlE_JEV7I0pQ7fhY4BIrrQ The YouTube data api is channel based you only have access to the single channel.
You are also using the "https://www.googleapis.com/auth/youtube.readonly" scope and the subscriptions list method documentation says it needs https://www.googleapis.com/youtube/v3/subscriptions which doesnt exist so I used https://www.googleapis.com/auth/youtube, but i would expect a different error message if this was the issue.
Notes:
I login when required and I allow any rights that are requested.
Make sure its to the correct channel.
I also use my own Google Developer Console account, my own API Key and my own OAuth 2.0 Client ID. I have enabled the YouTube Data API V3 and I have set up everything properly.
This has nothing to do with your access the client id, and api key just identify your application to Google they dont grant it access to anything. Thats what authorization is doing.
YouTube data api QuickStart for Authorizing for Web
Here is my QuickStart for this api.
<!DOCTYPE html>
<html>
<head>
<title>YouTube Data API Quickstart</title>
<meta charset="utf-8" />
</head>
<body>
<p>YouTube Data API Quickstart</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" onclick="handleAuthClick()">Authorize</button>
<button id="signout_button" onclick="handleSignoutClick()">Sign Out</button>
<pre id="content" style="white-space: pre-wrap;"></pre>
<script type="text/javascript">
/* exported gapiLoaded */
/* exported gisLoaded */
/* exported handleAuthClick */
/* exported handleSignoutClick */
// TODO(developer): Set to client ID and API key from the Developer Console
const CLIENT_ID = '[Redacted]';
const API_KEY = '[Redacted]';
// Discovery doc URL for APIs used by the quickstart
const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest';
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
const SCOPES = 'https://www.googleapis.com/auth/youtube';
let tokenClient;
let gapiInited = false;
let gisInited = false;
document.getElementById('authorize_button').style.visibility = 'hidden';
document.getElementById('signout_button').style.visibility = 'hidden';
/**
* Callback after api.js is loaded.
*/
function gapiLoaded() {
gapi.load('client', initializeGapiClient);
}
/**
* Callback after the API client is loaded. Loads the
* discovery doc to initialize the API.
*/
async function initializeGapiClient() {
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC],
});
gapiInited = true;
maybeEnableButtons();
}
/**
* Callback after Google Identity Services are loaded.
*/
function gisLoaded() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '', // defined later
});
gisInited = true;
maybeEnableButtons();
}
/**
* Enables user interaction after all libraries are loaded.
*/
function maybeEnableButtons() {
if (gapiInited && gisInited) {
document.getElementById('authorize_button').style.visibility = 'visible';
}
}
/**
* Sign in the user upon button click.
*/
function handleAuthClick() {
tokenClient.callback = async (resp) => {
if (resp.error !== undefined) {
throw (resp);
}
document.getElementById('signout_button').style.visibility = 'visible';
document.getElementById('authorize_button').innerText = 'Refresh';
await listSubscriptions();
};
if (gapi.client.getToken() === null) {
// Prompt the user to select a Google Account and ask for consent to share their data
// when establishing a new session.
tokenClient.requestAccessToken({prompt: 'consent'});
} else {
// Skip display of account chooser and consent dialog for an existing session.
tokenClient.requestAccessToken({prompt: ''});
}
}
/**
* Sign out the user upon button click.
*/
function handleSignoutClick() {
const token = gapi.client.getToken();
if (token !== null) {
google.accounts.oauth2.revoke(token.access_token);
gapi.client.setToken('');
document.getElementById('content').innerText = '';
document.getElementById('authorize_button').innerText = 'Authorize';
document.getElementById('signout_button').style.visibility = 'hidden';
}
}
/**
* Print metadata for first 10 Albums.
*/
async function listSubscriptions() {
let response;
try {
response = await gapi.client.youtube.subscriptions.list({
'pageSize': 10,
'part' :[ "snippet,subscriberSnippet,contentDetails" ],
"channelId": "UCyqzvMN8newXIxyYIkFzPvA",
'fields': 'items(id,snippet(title))',
});
} catch (err) {
document.getElementById('content').innerText = err.message;
return;
}
const subscriptions = response.result.items;
if (!subscriptions || subscriptions.length == 0) {
document.getElementById('content').innerText = 'No subscriptions found.';
return;
}
// Flatten to string to display
const output = subscriptions.reduce(
(str, subscription) => `${str}${subscription.snippet.title} (${subscription.id}\n`,
'albums:\n');
document.getElementById('content').innerText = output;
}
</script>
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
</body>
</html>

How to get google oauth refresh token in the lambda function by configuring the account linking section in alexa developer console?

I have referred this link https://medium.com/coinmonks/link-your-amazon-alexa-skill-with-a-google-api-within-5-minutes-7e488dc43168 and used same configuration as stated.
I am able to get access token in the lambda function var accesstoken =handlerInput.requestEnvelope.context.System.user.accessToken;
How to get refresh token in the handlerinput event by configuring the alexa developer console account linking section?
I have tried enable/disable skill in companion app,Tested with simulator,Removing alexa skill from the google auto access and then allowing access.
LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest' || (handlerInput.requestEnvelope.request.type === 'IntentRequest' && handlerInput.requestEnvelope.request.intent.name === 'LaunchRequest');
},
async handle(handlerInput) {
console.log('LAUNCH REQUEST CALLED');
const speechText = 'Welcome!';
if (handlerInput.requestEnvelope.context.System.user.accessToken === undefined) {
console.log('ACCESS TOKEN NOT FOUND IN LAUNCH REQUEST');
return handlerInput.responseBuilder
.speak("ACCESS TOKEN NOT FOUND IN LAUNCH REQUEST")
.reprompt("ACCESS TOKEN NOT FOUND IN LAUNCH REQUEST")
.withLinkAccountCard()
.withShouldEndSession(true)
.getResponse();
}
const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');
const SCOPES = ['https://www.googleapis.com/auth/userinfo.email','https://www.googleapis.com/auth/userinfo.profile','https://www.googleapis.com/auth/plus.me','https://www.googleapis.com/auth/tasks.readonly','https://www.googleapis.com/auth/tasks'];
function authorize() {
return new Promise((resolve) => {
const client_secret = process.env.client_secret;
const client_id = process.env.client_id;
const redirect_uris = ['*******************************', '*******************************', '*******************************'];
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
console.log('access token found : ' + handlerInput.requestEnvelope.context.System.user.accessToken);
oAuth2Client.credentials = { "access_token": handlerInput.requestEnvelope.context.System.user.accessToken };
The refresh token is not exposed to the Skill by Alexa, in other words : there is no way for your skill code to get access to the refresh token, this is entirely managed by Alexa. Alexa will use the refresh token behind the scene to ask your Identity Provider (Google in your case) a fresh token when your customer will access your skill and the access token is about to expire.
This is explained in Alexa Account Linking documentation at https://developer.amazon.com/docs/account-linking/account-linking-for-custom-skills.html#choose-auth-type-overview

Is possible to automate OAuth 2.0 implicit grant flow of Azure active directory in postman?

I'm trying to automate all the testing of an API. Currently is using a utentificacion using AAD.
The problem is: I can use the process of postman to get the token using OAuth2.0
Postman dialog
but I can't run a collection and do something like a trigger to get the token at the beginning. If i want to take the token I must push the button "Get new access token"
there is some way to do it automatically? or how can I create a flow to obtain the token?
Thanks!
You could use Pre-request Script to do it automatically. You just need to modify your required value and post it in the Pre-request Script of the postman. It had better in the parent collection, so it could inherit auth from the parent.
var getToken = true;
if (!pm.environment.get('accessTokenExpiry') ||
!pm.environment.get('currentAccessToken')) {
console.log('Token or expiry date are missing')
} else if (pm.environment.get('accessTokenExpiry') <= (new Date()).getTime()) {
console.log('Token is expired')
} else {
getToken = false;
console.log('Token and expiry date are all good');
}
if (getToken === true) {
pm.sendRequest({
url: 'https://login.microsoftonline.com/microsoft.onmicrosoft.com/oauth2/token',
method: 'POST',
header: 'Content-Type:application/x-www-form-urlencoded',
body: {
mode: 'raw',
raw: 'grant_type=implicit&client_id...'
}
}, function (err, res) {
console.log(err ? err : res.json());
if (err === null) {
console.log('Saving the token and expiry date')
var responseJson = res.json();
pm.environment.set('currentAccessToken', responseJson.access_token)
var expiryDate = new Date();
expiryDate.setSeconds(expiryDate.getSeconds() + responseJson.expires_in);
pm.environment.set('accessTokenExpiry', expiryDate.getTime());
}
});
}
For the code sample, you could refer to here.

Why is OAuth2 with Gmail Nodejs Nodemailer producing "Username and Password not accepted" error

OAuth2 is producing "Username and Password not accepted" error when try to send email with Gmail+ Nodejs+Nodemailer
Code - Nodejs - Nodemailer and xoauth2
var nodemailer = require("nodemailer");
var generator = require('xoauth2').createXOAuth2Generator({
user: "", // Your gmail address.
clientId: "",
clientSecret: "",
refreshToken: "",
});
// listen for token updates
// you probably want to store these to a db
generator.on('token', function(token){
console.log('New token for %s: %s', token.user, token.accessToken);
});
// login
var smtpTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
xoauth2: generator
}
});
var mailOptions = {
to: "",
subject: 'Hello ', // Subject line
text: 'Hello world ', // plaintext body
html: '<b>Hello world </b>' // html body
};
smtpTransport.sendMail(mailOptions, function(error, info) {
if (error) {
console.log(error);
} else {
console.log('Message sent: ' + info.response);
}
smtpTransport.close();
});
issues:
I used Google OAuth2 playground to create the tokens, https://developers.google.com/oauthplayground/
It looks to grab a valid accessToken ok, using the refreshToken, (i.e. it prints the new access token on the screen.) No errors until it tries to send the email.
I added the optional accessToken: but got the same error. ( "Username and Password not accepted")
I am not 100% sure about the "username", the docs say it needs a "user" email address - I guess the email of the account that created to token, but is not 100% clear. I have tried several things and none worked.
I have searched the options on the gmail accounts, did not find anything that looks wrong.
Also, when I did this with Java, it needed the google userID rather than the email address, not sure why this is using the email address and the Java is using the UserId.
nodemailer fails with a "compose" scope
The problem was the "scope"
it fails with:
https://www.googleapis.com/auth/gmail.compose
but works ok if I use
https://mail.google.com/
Simply just do the following:
1- Get credentials.json file from here https://developers.google.com/gmail/api/quickstart/nodejs press enable the Gmail API and then choose Desktop app
2- Save this file somewhere along with your credentials file
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://mail.google.com'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if(err){
return console.log('Error loading client secret file:', err);
}
// Authorize the client with credentials, then call the Gmail API.
authorize(JSON.parse(content), getAuth);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if(err){
return getNewToken(oAuth2Client, callback);
}
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function getAuth(auth){
}
3 - Run this file by typing in your terminal: node THIS_FILE.js
4- You'll have token.json file
5- take user information from credentials.json and token.json and fill them in the following function
const nodemailer = require('nodemailer');
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;
const email = 'gmail email'
const clientId = ''
const clientSecret = ''
const refresh = ''
const oauth2Client = new OAuth2(
clientId,
clientSecret,
);
oauth2Client.setCredentials({
refresh_token: refresh
});
const newAccessToken = oauth2Client.getAccessToken()
let transporter = nodemailer.createTransport(
{
service: 'Gmail',
auth: {
type: 'OAuth2',
user: email,
clientId: clientId,
clientSecret: clientSecret,
refreshToken: refresh,
accessToken: newAccessToken
}
},
{
// default message fields
// sender info
from: 'Firstname Lastname <your gmail email>'
}
);
const mailOptions = {
from: email,
to: "",
subject: "Node.js Email with Secure OAuth",
generateTextFromHTML: true,
html: "<b>test</b>"
};
transporter.sendMail(mailOptions, (error, response) => {
error ? console.log(error) : console.log(response);
transporter.close();
});
If your problem is the scopes, here is some help to fix
Tried to add this as an edit to the top answer but it was rejected, don't really know why this is off topic?
See the note here: https://nodemailer.com/smtp/oauth2/#troubleshooting
How to modify the scopes
The scopes are baked into the authorization step when you get your first refresh_token. If you are generating your refresh token via code (for example using the Node.js sample) then the revised scope needs to be set when you request your authUrl.
For the Node.js sample you need to modify SCOPES:
// If modifying these scopes, delete token.json.
-const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
+const SCOPES = ['https://mail.google.com'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
And then the call to oAuth2Client.generateAuthUrl will produce a url that will request authorization from the user to accept full access.
from the Node.js sample:
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});

Make my web app authorize with google oAuth

I am trying to login with Google oAuth. But when ever i try to login with oAuth it ask for permission. From the code i realize that there need to add the authorization . I have gone through web and found another way to make the app authorize which is complete different than what i have used here. Is there any way so that i can just modify or add a function so that i will be able to make my app authorize with google oAuth ? I am using php and javascript for my web app.
var loginFinished = function(authResult)
{
if (authResult['status']['signed_in']) {
var btnLogOut=document.getElementById("social-integration-logout");
accessToken=authResult['access_token'];
expiresIn=authResult['expires_in'];
console.log(authResult);
gapi.client.load('oauth2', 'v2', function()
{
gapi.client.oauth2.userinfo.get()
.execute(function(resp)
{
var id = resp.id;
});
});
} else {
console.log('Sign-in state: ' + authResult['error']);
}
};
var options = {
'callback': loginFinished,
'approvalprompt': 'force',
'clientid': '',
'scope': 'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
'requestvisibleactions': 'http://schemas.google.com/CommentActivity http://schemas.google.com/ReviewActivity',
'cookiepolicy': 'single_host_origin'
};
var renderBtn = function()
{
gapi.signin.render('btn_google_login', options);
}
Could you explain what issue you are having, i.e., what is not working? I notice that your script has 'approvalprompt': 'force', which will force the authorization dialog to always display. You may want to remove it so that returning users do not have to consent again. But I am not confident that this was your question.

Resources