I'm using the msal nodejs library. I have the following code
const ouathClient = new msal.ConfidentialClientApplication(msalConfig);
const tokenRequest = {
code: request.query.code,
scopes: process.env.OUTLOOK_OAUTH_SCOPES.split(','),
redirectUri: process.env.DOMAIN_NAME + "/outlook/oauth/redirect",
accessType: "offline"
};
const response = await ouathClient.acquireTokenByCode(tokenRequest);
const accessToken = response.accessToken;
const refreshToken = () => {
const tokenCache = ouathClient.getTokenCache().serialize();
const refreshTokenObject = (JSON.parse(tokenCache)).RefreshToken
const refreshToken = refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret;
return refreshToken;
}
const tokens = {
accessToken,
refreshToken: refreshToken()
}
IS this how to get the refresh token from the msal-node library? I created an app that connects doctors and patients. I want patients to be able to book time on a doctor's outlook calendar. I need to get access to the doctor's outlook account. I can use the access token to get access to his calendar, but that expires.
How do I refresh the token after some time?
Related
I am going to get my subscription list with the help of the YouTube api. I wrote this piece of code.
const { google } = require('googleapis');
const oauth2 = google.oauth2('v2');
const express = require('express')
const app = express()
const port = 3000
const oauth2Client = new google.auth.OAuth2(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxxxxxxxxx",
"http://localhost:3000/auth/google/callback"
);
google.options({
auth: oauth2Client
});
// generate a url that asks permissions for Blogger and Google Calendar scopes
const scopes = [
'https://www.googleapis.com/auth/youtube',
'https://www.googleapis.com/auth/youtube.channel-memberships.creator'
];
const url = oauth2Client.generateAuthUrl({
// 'online' (default) or 'offline' (gets refresh_token)
access_type: 'offline',
// If you only need one scope you can pass it as a string
scope: scopes
});
app.get('/',(req,res,next)=>{
res.send(url);
})
let tok = "";
app.get('/auth/google/callback',(req,res,next)=>{
res.send('Susses Authrations');
console.log("Code authrations : "+req.query.code);
const {tokens} = oauth2Client.getToken(req.query.code)
oauth2Client.setCredentials(tokens);
oauth2Client.on('tokens', (tokens) => {
if (tokens.refresh_token) {
// store the refresh_token in my database!
console.log("refresh_token : "+ tokens.refresh_token);
tok = tokens.access_token;
}
console.log("Assess Token : "+ tokens.access_token);
});
})
app.get('/youtube',(req,res,next)=>{
const youtube = google.youtube('v3',{
'access_token':oauth2Client.credentials.access_token,
'refresh_token':oauth2Client.credentials.refresh_token,
'api_key':oauth2Client.credentials.api_key
});
youtube.channels.list({
"part": [
"snippet,contentDetails,statistics"
],
"id": [
"UC_x5XG1OV2P6uZZ5FSM9Ttw"
]
}).then(e=>{
console.log(e.request)
})
})
app.listen(port,()=>{
console.log("Hello World")
});
But unfortunately I encounter an error (Error: No access, refresh token or API key is set.) Which apparently does not recognize my refresh token. I am a novice and thank you for guiding me. I also use the clinet id and clinet secret I also built a console inside Google and activated YouTube related libraries.
I am using launchWebAuthFlow in a service worker to authenticate users who choose to backup the extension settings in their Google Drive.
When a user clicks the login button, it sends a message to the service worker (MV3, perviously "background script"), who does this:
const redirectUrl = await browser.identity.launchWebAuthFlow({
'url': _createAuthEndpoint(),
'interactive': true
})
const url = new URL(redirectUrl);
const urlParams = new URLSearchParams(url.hash.slice(1));
const params = Object.fromEntries(urlParams.entries());
await browser.storage.local.set({googleToken: params.access_token});
Helper method to construct auth url:
function _createAuthEndpoint() {
const redirectURL = browser.identity.getRedirectURL();
const { oauth2 } = browser.runtime.getManifest();
const clientId = oauth2.client_id;
const authParams = new URLSearchParams({
client_id: clientId,
response_type: 'token',
redirect_uri: redirectURL,
scope: 'openid ' + oauth2.scopes.join(' '),
});
return `https://accounts.google.com/o/oauth2/auth?${authParams.toString()}`;
}
It works well for about an hour, after that the token get invalidated and I need to get a new token. If i try to use launchWebAuthFlow with interactive: false I get an error "user interaction required"
Is there a way to have the token refresh without user interaction?
I hope you can help me: I currently develop an app which needs access to the users calendar (outlook-calendar) to find free meeting slots (other users will be able to see and then select one of the free slots - similar to calendly). For that I use msal-node to authenticate against azureAD. But my use case needs "everytime"-access to the calendars from all users. This is why I want to get an refresh_token. The docs of msal-node say that I should provide the offline_scope to get an refreshtoken while doing the OAuth-process.
My problem is that I receive an access_token and id_token and so on, but no refreshtoken. The Azure-response further shows a successful answer but when I take a look into the returned scopes I cannot find offline_scope.
You can see the returned scopes here
What should I do?
I use a cofidentalClientApplication msal-node instance:
const oauth2Client = MicrosoftClient.Connection
const authCodeUrlParameters = {
scopes: ["offline_access", "user.read"],
forceRefresh: true,
redirectUri: "http://localhost:3000/outlookRedirect",
}
try {
console.log("GDFHGJF")
return oauth2Client.getAuthCodeUrl(authCodeUrlParameters)
}
After receiving the code from Azure, I process it via:
const oauth2Client = MicrosoftClient.Connection
const tokenRequest = {
code: code,
scopes: ["user.read", "offline_access"],
forceRefresh: true,
redirectUri: "http://localhost:3000/outlookRedirect",
//client_secret: process.env.MICROSOFTCLIENTSECRET,
}
const testus = await oauth2Client.acquireTokenByCode(tokenRequest)
const tokenRequest2 = {
scopes: ["user.read", "offline_access"],
forceRefresh: true,
redirectUri: "http://localhost:3000/outlookRedirect",
account: testus.account,
}
oauth2Client
.acquireTokenSilent(tokenRequest2)
.then((response) => {
console.log("\nResponse: \n:", response)
})
.catch((error) => {
console.log(error)
})
return
What is my fault? I appreciate any kind of help!
Thank you in advance,
Lukas
after calling 'acquireTokenByCode' , 'pca' now has the refresh token. const tokenCache = pca.getTokenCache().serialize(); const refreshTokenObject = (JSON.parse(tokenCache)).RefreshToken const refreshToken = refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret;
Below is a complete snippet of How to get the Refresh and Access token.
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const express = require("express");
const msal = require('#azure/msal-node');
const SERVER_PORT = process.env.PORT || 3000;
const REDIRECT_URI = "http://localhost:3000/redirect";
// Before running the sample, you will need to replace the values in the config,
// including the clientSecret
const config = {
auth: {
clientId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
authority: "https://login.microsoftonline.com/84fb56d3-e15d-4ae1-acd7-cbf83c4c0af3",
clientSecret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Create msal application object
const pca = new msal.ConfidentialClientApplication(config);
// Create Express App and Routes
const app = express();
app.get('/', (req, res) => {
const authCodeUrlParameters = {
scopes: ["user.read","offline_access"],
redirectUri: REDIRECT_URI,
prompt:'consent'
};
// get url to sign user in and consent to scopes needed for application
pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
res.redirect(response);
}).catch((error) => console.log(JSON.stringify(error)));
});
app.get('/redirect', (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ["user.read","offline_access"],
redirectUri: REDIRECT_URI,
accessType: 'offline',
};
pca.acquireTokenByCode(tokenRequest).then((response) => {
const accessToken = response.accessToken;
const refreshToken = () => {
const tokenCache = pca.getTokenCache().serialize();
const refreshTokenObject = (JSON.parse(tokenCache)).RefreshToken
const refreshToken = refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret;
return refreshToken;
}
const tokens = {
accessToken,
refreshToken:refreshToken()
}
console.log(tokens)
res.sendStatus(200);
}).catch((error) => {
console.log(error);
res.status(500).send(error);
});
});
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))
msal-node does not expose the refresh token to the end user by design. It is stored and used internally under the hood when you need a new access token. You should call acquireTokenSilent each time you need an access token and msal-node will manage the tokens by either returning a cached token to you or using the refresh token to acquire a new access token.
For more context: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/2836
In addition to the accepted answer, its important to note that the MSAL cache can have many authenticated users (and lots of refresh tokens). Here is my solution to extract the exact refresh token for a specific user.
I use this on each login to exact the individuals refresh token and store it.
public extractRefresh = ( homeAccountId : string ) : string =>
{
try
{
const tokenCache = this.msalClientApp.getTokenCache().serialize();
const refreshTokenObject = ( JSON.parse( tokenCache ) ).RefreshToken;
let refreshToken = '';
Object.entries( refreshTokenObject ).forEach( ( item : any ) =>
{
if ( item[1].home_account_id === homeAccountId )
{
refreshToken = item[1].secret;
}
});
return refreshToken;
}
catch
{
return '';
}
}
Building a basic application where users can find Service Providers using MEAN Stack, and after negotiations are over, agreements are auto generated and have to be signed by both parties.
Got Stuck on generation of JWT Token for authentication.
Steps I followed are:
Generate a url for obtaining consent from user and pass it to frontend. Users will be redirected and permissions can be granted from there.
var url = "https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature&client_id=42017946-xxxx-xxxx-xxxx-81b0ca97dc9a&redirect_uri=http://localhost:4200/authorization_code/callback";
res.status(200).json({
status: 1,
message: 'Fetched',
value: url
});
After successful redirection with code in URL, API call is made to backend for the generation of JWT token.
Token is generated as follows:
var jwt = require('jsonwebtoken');
var privateKey = fs.readFileSync(require('path').resolve(__dirname, '../../src/environments/docusign'));
const header = {
"alg": "RS256",
"typ": "JWT"
};
const payload = {
iss: '42017946-xxxx-xxxx-a5cd-xxxxxx',
sub: '123456',
iat: Math.floor(+new Date() / 1000),
aud: "account-d.docusign.com",
scope: "signature"
};
var token = jwt.sign(payload, privateKey, { algorithm: 'RS256', header: header });
Private key used above is from docusign admin panel.
iss -> Integration key against my app.
sub -> user id in the drop down of user symbol in admin panel
Obtain the access token
const axios = require('axios');
axios.post('https://account-d.docusign.com/oauth/token',
{
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: token
})
.then(resposne => {
console.log(response);
})
.catch(err => {
if (err.response) {
console.log(err);
} else if (err.request) {}
else {}
})
But I am constantly getting error: { error: 'invalid_grant', error_description: 'no_valid_keys_or_signatures' }
I would suggest using the node.JS SDK or npm package and using the build-it JWT method to authenticate. The code would look like this:
(click here for GitHub example)
DsJwtAuth.prototype.getToken = async function _getToken() {
// Data used
// dsConfig.dsClientId
// dsConfig.impersonatedUserGuid
// dsConfig.privateKey
// dsConfig.dsOauthServer
const jwtLifeSec = 10 * 60, // requested lifetime for the JWT is 10 min
scopes = "signature", // impersonation scope is implied due to use of JWT grant
dsApi = new docusign.ApiClient();
dsApi.setOAuthBasePath(dsConfig.dsOauthServer.replace('https://', '')); // it should be domain only.
const results = await dsApi.requestJWTUserToken(dsConfig.dsClientId,
dsConfig.impersonatedUserGuid, scopes, rsaKey,
jwtLifeSec);
const expiresAt = moment().add(results.body.expires_in, 's').subtract(tokenReplaceMin, 'm');
this.accessToken = results.body.access_token;
this._tokenExpiration = expiresAt;
return {
accessToken: results.body.access_token,
tokenExpirationTimestamp: expiresAt
};
I'm trying to work with the google API's for the first time, and when I attempt to make a request to the gmail API I'm getting a "precondition check failed" error. I am using a service account authorization, not Oauth2 user consent. Things I've tried:
Authorized "domain wide delegation" for the service account.
Ensured the APP is trusted in the G suite account.
Ensured service account role is "owner"
Enabled domain wide delegation for the client ID of the service account in the g suite admin panel.
This is an adapted sample from the Node client library, but the sample did not use service account auth so I wasn't able to use the sample directly.
const path = require('path');
const {google} = require('googleapis');
const gmail = google.gmail('v1');
async function runSample() {
// Obtain user credentials to use for the request
const auth = new google.auth.GoogleAuth({
keyFile: path.resolve(__dirname, 'google-key.json'),
scopes: ['https://www.googleapis.com/auth/gmail.readonly'],
});
google.options({auth});
const res = await gmail.users.messages.list({userId: 'me'}); // have tried with my gsuite email address as well
console.log(res.data);
return res.data;
}
if (module === require.main) {
runSample().catch(console.error);
}
module.exports = runSample;
Returning error with message: Error: Precondition check failed.
After searching the dark web for eternity, I found a link to a github issue that described how to authenticate as a service using JWT auth.
This is a working version of what I was trying to accomplish:
const path = require('path');
const {google} = require('googleapis');
async getMessageList(userId, qty) {
const JWT = google.auth.JWT;
const authClient = new JWT({
keyFile: path.resolve(__dirname, 'google-key.json'),
scopes: ['https://www.googleapis.com/auth/gmail.readonly'],
subject: 'admin#example.com' // google admin email address to impersonate
});
await authClient.authorize(); // once authorized, can do whatever you want
const gmail = google.gmail({
auth: authClient,
version: 'v1'
});
const response = await gmail.users.messages.list({
includeSpamTrash: false,
maxResults: qty,
q: "",
userId: userId
});
// the data object includes a "messages" array of message data
return response.data;
}