Microsoft login isn't returning refresh token - javascript

I am trying to create an integration between my application and Microsoft calendar,
I have a page which allows the user to connect his account with my App
using the following code
const msalInstance = new msal.PublicClientApplication({
auth: {
clientId: '<client-id>',
},
});
msalInstance.loginRedirect({
scopes: ['user.read', 'calendars.readwrite', 'offline_access', 'openid'],
grant_type: 'authorization_code',
});
The login is working and returns token and I can create an event easily
But the token expires after 1 hour, and the response doesn't contain a refresh token
Is there a way to generate a refresh token, or create a token that will stay active for a longer period.
Because I want the user to login once and just create an integration between my app and azure.
Same as the outlook calendar for slack.
my azure application has the needed API permissions
openid offline_access am I missing something?

as https://stackoverflow.com/users/519348/tzhx mentioned, it was the MSAL JS library that caused the issue
I handled the login manually and I was able to see the refresh token.
Thanks.

Related

React + rails Application AAD and OpenID connect

I'm deeveloping an application that uses React (nextjs to be more accurate) and Ruby on rails as my back end. Althought, i'm trying to use AAD as my Identiity provider.
I need to know if my approuch using OIDC it's correct and how to implement it as well.
Following the protocol rules (https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc), the first thing i'm trying, and failing, is to get user sign-in to get the authorization_code and then exchange that to an access_token in my backend.
window.location.assign(
url.build({
host: process.env.NEXT_PUBLIC_AAD_AUTHORITY,
path: "/oauth2/v2.0/authorize",
query: {
response_type: "code id_token",
client_id: process.env.NEXT_PUBLIC_AAD_CLIENT_ID,
scope: "openid https://graph.microsoft.com/.default",
redirect_uri: "http://localhost:3000/callback",
response_mode: "form_post",
nonce: code_challenge,
code_challenge_method: "S256",
code_challenge: code_challenge,
},
})
);
Since i'm using OICD, the response mode must be "form_post" or "fragment".
form_data_request
My problem is that i have no idea how to receive this kind of response, since i'm not using fetch (generates CORS errors) that its it thenable.
So....any thoughts?
Your approach using OIDC is correct because rails fully support OIDC. To implement you need to configure the tenant in Azure AD then register a new app in you tenant Azure Active Directory. Go to Azure portal and click on “App registration” and on “New registration”. Lastly, move to your rails app and then integrate your application with Azure active directory. Here is a complete guide that can direct you.

Single flow: sign user in via Google oAuth AND grant offline/server access?

I'm trying to implement Google sign-in and API access for a web app with a Node.js back end. Google's docs provide two options using a combo of platform.js client-side and google-auth-library server-side:
Google Sign-In with back-end auth, via which users can log into my app using their Google account. (auth2.signIn() on the client and verifyIdToken() on the server.)
Google Sign-in for server-side apps, via which I can authorize the server to connect to Google directly on behalf of my users. (auth2.grantOfflineAccess() on the client, which returns a code I can pass to getToken() on the server.)
I need both: I want to authenticate users via Google sign-in; and, I want to set up server auth so it can also work on behalf of the user.
I can't figure out how to do this with a single authentication flow. The closest I can get is to do the two in sequence: authenticate the user first with signIn(), and then (as needed), do a second pass via grantOfflineAccess(). This is problematic:
The user now has to go through two authentications back to back, which is awkward and makes it look like there's something broken with my app.
In order to avoid running afoul of popup blockers, I can't give them those two flows on top of each other; I have to do the first authentication, then supply a button to start the second authentication. This is super-awkward because now I have to explain why the first one wasn't enough.
Ideally there's some variant of signIn() that adds the offline access into the initial authentication flow and returns the code along with the usual tokens, but I'm not seeing anything. Help?
(Edit: Some advice I received elsewhere is to implement only flow #2, then use a secure cookie store some sort of user identifier that I check against the user account with each request. I can see that this would work functionally, but it basically means I'm rolling my own login system, which would seem to increase the chance I introduce bugs in a critical system.)
To add an API to an existing Google Sign-In integration the best option is to implement incremental authorization. For this, you need to use both google-auth-library and googleapis, so that users can have this workflow:
Authenticate with Google Sign-In.
Authorize your application to use their information to integrate it with a Google API. For instance, Google Calendar. 
For this, your client-side JavaScript for authentication might require some changes to request
offline access:
$('#signinButton').click(function() {
auth2.grantOfflineAccess().then(signInCallback);
});
In the response, you will have a JSON object with an authorization code:
{"code":"4/yU4cQZTMnnMtetyFcIWNItG32eKxxxgXXX-Z4yyJJJo.4qHskT-UtugceFc0ZRONyF4z7U4UmAI"}
After this, you can use the one-time code to exchange it for an access token and refresh token.
Here are some workflow details:
The code is your one-time code that your server can exchange for its own access token and refresh token. You can only obtain a refresh token after the user has been presented an authorization dialog requesting offline access. If you've specified the select-account prompt in the OfflineAccessOptions [...], you must store the refresh token that you retrieve for later use because subsequent exchanges will return null for the refresh token
Therefore, you should use google-auth-library to complete this workflow in the back-end. For this,
you'll use the authentication code to get a refresh token. However, as this is an offline workflow,
you also need to verify the integrity of the provided code as the documentation explains:
If you use Google Sign-In with an app or site that communicates with a backend server, you might need to identify the currently signed-in user on the server. To do so securely, after a user successfully signs in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity of the ID token and use the user information contained in the token
The final function to get the refresh token that you should persist in your database might look like
this:
const { OAuth2Client } = require('google-auth-library');
/**
* Create a new OAuth2Client, and go through the OAuth2 content
* workflow. Return the refresh token.
*/
function getRefreshToken(code, scope) {
return new Promise((resolve, reject) => {
// Create an oAuth client to authorize the API call. Secrets should be
// downloaded from the Google Developers Console.
const oAuth2Client = new OAuth2Client(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// Generate the url that will be used for the consent dialog.
await oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope,
});
// Verify the integrity of the idToken through the authentication
// code and use the user information contained in the token
const { tokens } = await client.getToken(code);
const ticket = await client.verifyIdToken({
idToken: tokens.id_token!,
audience: keys.web.client_secret,
});
idInfo = ticket.getPayload();
return tokens.refresh_token;
})
}
At this point, we've refactored the authentication workflow to support Google APIs. However, you haven't asked the user to authorize it yet. Since you also need to grant offline access, you should request additional permissions through your client-side application. Keep in mind that you already need an active session.
const googleOauth = gapi.auth2.getAuthInstance();
const newScope = "https://www.googleapis.com/auth/calendar"
googleOauth = auth2.currentUser.get();
googleOauth.grantOfflineAccess({ scope: newScope }).then(
function(success){
console.log(JSON.stringify({ message: "success", value: success }));
},
function(fail){
alert(JSON.stringify({message: "fail", value: fail}));
});
You're done with the front-end changes and you're only missing one step. To create a Google API's client in the back-end with the googleapis library, you need to use the refresh token from the previous step.
For a complete workflow with a Node.js back-end, you might find my gist helpful.
While authentication (sign in), you need to add "offline" access type (by default online) , so you will get a refresh token which you can use to get access token later without further user consent/authentication. You don't need to grant offline later, but only during signing in by adding the offline access_type. I don't know about platform.js but used "passport" npm module . I have also used "googleapis" npm module/library, this is official by Google.
https://developers.google.com/identity/protocols/oauth2/web-server
https://github.com/googleapis/google-api-nodejs-client
Check this:
https://github.com/googleapis/google-api-nodejs-client#generating-an-authentication-url
EDIT: You have a server side & you need to work on behalf of the user. You also want to use Google for signing in. You just need #2 Google Sign-in for server-side apps , why are you considering both #1 & #2 options.
I can think of #2 as the proper way based on your requirements. If you just want to signin, use basic scope such as email & profile (openid connect) to identify the user. And if you want user delegated permission (such as you want to automatically create an event in users calendar), just add the offline access_type during sign in. You can use only signing in for registered users & offline_access for new users.
Above is a single authentication flow.

How to Get a valid access token for my API and Microsoft Graph from Azure Active Directory?

I am trying to set up a spa javascript app which logs the user into our azure active directory tenant and then retrieves profile information from microsoft graph and calls an azure function written in c# core (my API).
I have separate application registrations set up for my website and the api in azure active directory.
I'm using the MSAL.js library in the javascript spa website and I'm using the newer microsoft identity / v2.0 endpoints.
The SPA app signs into active directory as expected and is able to use the access token to make the call to graph for the profile information. In my azure function I validate the token and this fails with the error "IDX10511: Signature validation failed. Keys tried: ....."
Now if I remove Microsoft graph from the scopes when requesting a token I get a token that when passed to the azure function validates perfectly well but I can no longer retrieve profile data in the spa app?
How do I get both to work?
Its also worth noting that ive tested the tokens with jwt.io and it is also unable to verify the signature when graph is used.
Heres how I'm getting my token:
var msalConfig = {
auth: {
redirectUri: window.location.origin, // forces top level instead of specific login pages - fixes deep link issues.
clientId: "Client ID of the website app", //This is your client ID
authority:
"https://login.microsoftonline.com/my-tennant-guid" //This is your tenant info
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
const msalUserAgent = new Msal.UserAgentApplication(msalConfig);
var requestObj = {
scopes: ["User.Read", "api://MyApi/Access"]
};
//when the spa starts up I login using redirects
msalUserAgent.loginRedirect(requestObj);
//then before calling an api I request a token using this method
acquireTokenSilent() {
var promise = msalUserAgent.acquireTokenSilent(requestObj);
return promise;
},
Try specifying the scopes as scopes: ["User.Read"] in the acquireTokenSilent() function.
Since an access token is only valid for one API.
If you need two, call acquireTokenSilent twice with different scopes.
It's okay to specify scopes for two APIs when signing in, but not when getting tokens. A token has an audience that specifies the target API. So you can't use a token for one API against another. And that's why it's only valid for one API.

Microsoft Graph API 401 when accessing calendar events

I'm currently trying to create a basic website to run locally on a raspberry pi that shows me my outlook calendar for the day. However, when I try and access the https://graph.microsoft.com/v1.0/me/events endpoint, I get a 401 unauthorized error.
This is for a simple node.js express server, with all of the code for interacting with the graph api coming from the javascript quickstart app provided by Microsoft. The quickstart app works fine, and I'm able to see the json response for the https://graph.microsoft.com/v1.0/me endpoint that is part of the demo, but changing the endpoint causes a 401.
I've made sure in the azure app registration page that I've enabled the Calendar.read permission, and in my app I've added the calendar.read permission to my scope. When I login, the login popup window asks for permission to read my calendar which to me says that the app should have access to my calendar.
My application configuration (again based off of the quickstart app) looks like this:
const applicationConfig = {
clientID: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
authority: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
graphScopes: ['calendars.read', 'tasks.read'],
graphEndpoint: 'https://graph.microsoft.com/v1.0/me/events',
};
Other than these changes, the rest of the code is identical to the sample code.
You should also include User.Read in your scopes. From the documentation on User.Read:
Allows users to sign-in to the app, and allows the app to read the profile of signed-in users. It also allows the app to read basic company information of signed-in users.
const applicationConfig = {
clientID: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
authority: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
graphScopes: ['Users.Read', 'Calendars.Read', 'Tasks.Read'],
graphEndpoint: 'https://graph.microsoft.com/v1.0/me/events',
};

How to use a Refresh token to get access to google drive files

I am using the Drive API and the Google Sheets API to list users private and public spreadsheets using Google Console app.
I want the user to log in to our app the first time to view their files (this process is working). Now I want this user can access his files list without login after first-time authorization.
According to the Google guide, for this, I need to generate the access token and refresh token. I am having both the access token and refresh token.
here:
{
"access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in":3920,
"token_type":"Bearer",
"refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}
Now how I can use the refresh token and access token for users to avoid login in again and again. Please give a step-by-step process.
{
"access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in":3920,
"token_type":"Bearer",
"refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}
With the access_token you can get data from your google APIs. The token is specific to the API you have got it from. Use it as Authorization: Bearer 1/fFAGRNJru1FTz70BzhT3Zg).
You can use the refresh token to renew the access token. Access tokens expire after some time (3920 secs in your case).
The general Google API docs just say:
Access tokens have limited lifetimes. If your application needs access to a Google API beyond the lifetime of a single access token, it can obtain a refresh token. A refresh token allows your application to obtain new access tokens.
See https://developers.google.com/identity/protocols/oauth2?hl=en
Here's a step by step instruction available for the playground:
https://github.com/ivanvermeyen/laravel-google-drive-demo/blob/master/README/2-getting-your-refresh-token.md
Please have a look at this SO question/answer. An answer in this URL provides the following code:
window.gapi.client.init({
apiKey: this.GOOGLE.API_KEY,
clientId: this.GOOGLE.CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES
}).then(()=>{
const authInstance = window.gapi.auth2.getAuthInstance();
authInstance.grantOfflineAccess()
.then((res) => {
console.log(res);
this.data.refreshToken = res.code;
});
});

Categories

Resources