Microsoft Graph - How to get App to read user calendar events - microsoft-graph-api

I'm trying to create a Node/Javascript service that will read a user's outlook calendar.
This took about 2 hours to get the front and backend fully setup and working with Google Calendar.
I'm on my 2nd week of trying to get this to work with Microsoft Graph.
The user giving authorization will be outside of my app's tenant/organization.
I've registered an app in Azure, and given it the Calendars.ReadWrite and Users.Read permissions.
I've granted Admin consent for the default directory (someone suggested this).
I've setup the front end authorization page for the user to give authorization for the app permissions defined in the scope.
The front end user authorization works fine, the authorization page pops up describing the access that the app is requesting and on confirmation it returns the user's access_token, refresh_token, does the redirect, etc.
The part I'm having the problem with is for the Node application to read that user's calendar events.
Depending on whether I add the user's accessToken to the call, I get different errors returned.
I have created 2 functions so far in the Node application.
The first function getMSClient populates the authProvider with clientId, clientSecret, tenantId, and gets/returns a client with the authProvider information
This function "seems" to be working fine. If I do an output of the client I get an object with config: {...lots of properties}
The second function is where I'm trying to make the call to read the user calendar, but I get all sorts of errors,
If I use the client and make the call with:
const events = await client.api(users/${email}/calendar/getSchedule).get()
I receive an object with a statusCode of 401
If I use the client and add the user's accessToken to the header and make the call with:
const events = await client.api(users/${email}/calendar/getSchedule)..header('Authorization', Bearer ${userAccessToken}).get()
I get an error message about 'CompactToken parsing failed with error code: 80049217'
with 401 statusCode and code 'InvalidAuthenticationToken'
Here is the code:
const getMSClient = async () => {
const credsJSON = await fs.readFile(CREDENTIALS_PATH);
const creds = JSON.parse(credsJSON);
const {tenantId, clientId, clientSecret } = creds;
const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
const authProvider = new TokenCredentialAuthenticationProvider(
credential, {
scopes: ['https://graph.microsoft.com/.default']
});
const client = await Client.initWithMiddleware({
debugLogging: true,
authProvider
});
return client;
}
const getAllEvents = async () => {
const userAccessToken = 'eyJ0eXAiOiJKV1QiLCJ .........';
const email = 'user#outlook.com'
const client = await getMSClient();
const events = await client.api(`users/${email}/calendar/getSchedule`).header('Authorization', `Bearer ${userAccessToken}`).get()
console.log(`events: `);
console.dir(events)
}
getAllEvents();

Related

Google Identity: what is the recommended way to get a new access token after it expires

I am getting an access token like this:
private async _promptForToken(scopes: string[], prompt: "none" | "consent"): Promise<string> {
const that = this
return new Promise(resolve => {
const tokenClient = google.accounts.oauth2.initTokenClient({
client_id: this.clientId,
scope: scopes.join(' '),
callback: function (tokenResponse) {
that._storeTokenResponse(tokenResponse)
resolve(tokenResponse.access_token)
}
})
tokenClient.requestAccessToken({prompt})
})
}
I storing the token in local storage. If I leave the browser for an hour, so the access token expires, and I come back to my app and click a button that requires a new access token, I am requesting the new token using this code:
this._promptForToken(scopes, 'none')
In other words, I am asking for the same access permissions, but without consent. When I do that I get back a response like this:
{error_subtype: "access_denied", error: "interaction_required"}
Which I can't find documented anywhere, but that's another issue.
If instead, I ask for a new access token using consent i.e.
this._promptForToken(scopes, 'consent')
The Google dialog box for permissions pops up for a second, then disappears, which is horrible UX. And this will happen every time an access token expires. Horrible I say!
What is the recommended way to request a new access token?
Context: browser only, so implicit flow only, I do not want to have to maintain refresh tokens in the backend.

Example of Login with Tiktok (web) flow

I would like a user to login with tikok on the web and get his basic information like:
avarat_url
union_id (uniq user identifier provided by tiktok)
display_name
The Tiktok Login Kit for Web Documentation seems to be missing a full example on how to implement the full sequence of calls. Also some things are not explained at all (like the callback URL). Can someone share their full solution with code example on how to integrate tiktok login onto a webpage.
Heres a full example of the tiktok login for web implementation:
setup a tiktok developer account https://developers.tiktok.com/
create a new app, this will generate a CLIENT_KEY and CLIENT_SECRET.
create 2 backend endpoints that you control, for example:
https://example.com/auth : builds a tiktok URL and redirects the user to that endpoint (where the user will be prompted to login).
https://example.com/authCallback : once the user has finished the login with tiktok flow, tiktok sends an authorizationResponse to this endpoint. The authorizationResponse contains info that you need to fetch the users data.
in section "Platform Info": insert the callback URL and redirect domain. The callback URL being the second of the 2 server endpoints listed above. Tiktok will send the authorizationResponse to that URL once the user successfully loggs in and grants or declines the required permissions. In redirect domain simply add the domain without the exact path.
fill out all info for your app and wait for approval, this can take up to 1-3 days.
once approved, you are ready to implement the full flow, which consists of multiple steps/requests.
(A) send the user from your frontend to your first backend endpoint https://example.com/auth. From there, the user will be redirected to the tiktok auth page.
(B) once the user finished the authorization, tiktok sends a authorizationResponse to your callback URL (https://example.com/authCallback), which contains a variable code. With the code you can request the access_token and open_id of the user.
(C) use the access_token and open_id to request basic user info.
(A) Send User to Tiktok Authentication Page
In your frontend, redirect the user to https://example.com/auth. Then run the following nodejs backend code on your /auth route. For this example we use an express app (req = request object, res = response object):
// IMPORTANT, it is your responsibility to store a csrf token
// in your database, to be able to prevent xss attacks, read more
// here (section 2.1) => https://developers.tiktok.com/doc/login-kit-web
const createCsrfState = () => Math.random().toString(36).substring(7);
const csrfState = createCsrfState();
res.cookie('csrfState', csrfState, { maxAge: 60000 });
let url = 'https://open-api.tiktok.com/platform/oauth/connect/';
url += `?client_key=${CLIENT_KEY}`;
url += '&scope=user.info.basic';
url += '&response_type=code';
url += `&redirect_uri=${encodeURIComponent('https://example.com/authCallback')}`;
url += '&state=' + csrfState;
// redirect the user to the generated URL
// user will be prompted to login with tiktok
// and authorize needed permissions
res.redirect(url);
This code redirects the user to a tiktok url, where the user is prompted to sign in with tiktok and grant access.
(B) Handle authorizationResponse, use code to get access_token and open_id
Once the user finished the login process, tiktok sends an authorizationResponse to your second backend server endpoint https://example.com/authCallback. In that callback you recieve variables state and code.
// express example with
// `req` = request object
// `res` = response object
// check if the csrf token is valid
// its the developers responsibility
// to setup a validation logic.
if (!validateCsrfToken(req.query.state)) {
throw new Error("invalid csrf token");
}
async function getAccessTokenAndOpenId(code, TIKTOK_CLIENT_KEY, TIKTOK_CLIENT_SECRET) {
let urlAccessToken = 'https://open-api.tiktok.com/oauth/access_token/';
urlAccessToken += '?client_key=' + TIKTOK_CLIENT_KEY;
urlAccessToken += '&client_secret=' + TIKTOK_CLIENT_SECRET;
urlAccessToken += '&code=' + code;
urlAccessToken += '&grant_type=authorization_code';
const resp = await axios.post(urlAccessToken);
return {
accessToken: resp.data.data.access_token,
openId: resp.data.data.open_id,
};
}
const code = req.query.code;
const { openId, accessToken } = await getAccessTokenAndOpenId(code, TIKTOK_CLIENT_KEY, TIKTOK_CLIENT_SECRET);
(C) Get basic user info
async function getBasicInfo(accessToken, openId) {
let urlBasicInfo = `https://open-api.tiktok.com/user/info/`;
const data = {
access_token: accessToken,
open_id: openId,
fields: [
"open_id",
"union_id",
"avatar_url",
"avatar_url_100",
"avatar_url_200",
"avatar_large_url",
"display_name",
],
};
const resp = await axios.post(urlBasicInfo, data);
return resp.data.data.user;
}
const userBasicInfo = await getBasicInfo(accessToken, openId);
// 🥳 done!

How to acces some one's inbox with Application permission using MS Graph Api

i want to use the Graph Api to access the o365 inbox to process the incoming mails.without user signed in ( Application permission) .
with that i can able to get the access token but i cant able to access the inbox. and the code as follows.
//Defining app
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();
//Getting access token
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
//graph service client
GraphServiceClient graphClient = new GraphServiceClient(new
DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
}));
//accessing Ms graph api
var messages = await graphClient.Me.Messages
.Request()
.Select(e => new {
e.Sender,
e.Subject
})
.GetAsync();
Code: BadRequest
Message: Current authenticated context is not valid for this request. This occurs when a request is made to an endpoint that requires user sign-in. For example, /me requires a signed-in user. Acquire a token on behalf of a user to make requests to these endpoints. Use the OAuth 2.0 authorization code flow for mobile and native apps and the OAuth 2.0 implicit flow for single-page web apps.
Inner error
I dont know what i am doing wrong here please help me
You cannot use .Me since there is no authenticated user. You need to instead do something like:
//accessing Ms graph api
var messages = await graphClient.Users["user-id"].Messages
.Request()
.Select(e => new {
e.Sender,
e.Subject
})
.GetAsync();
Where user-id is either the user's id from Graph, or their UPN (typically their email address).

Reproducing an ADAL.JS-authenticated request in Postman

I have a .NET Web API and a small vanilla-JS app using ADAL.js, and I've managed to make them talk nicely to each-other and authenticate correctly.
If I console.log the token returned from adalAuthContext.acquireToken() and manually enter it as Authorization: Bearer {{token}} in Postman, I can also get a valid, authenticated, response from my backend.
However, I can't figure out how to configure Postman's built-in OAuth2.0 authentication UI to get me tokens automatically. I have managed to get tokens in several ways, but none of them are accepted by the backend.
How do I configure Postman to get a token the same way the ADAL.js library does?
For completeness, here's some code:
Backend configuration:
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters { ValidAudience = "<app-id>" },
Tenant = "<tenant>",
AuthenticationType = "WebAPI"
});
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
}
ADAL.js configuration:
const backendUrl = 'http://localhost:55476';
const backendAppId = '<app-id>';
const authContext = new AuthenticationContext({
clientId: backendAppId,
tenant: '<tenant>',
endpoints: [{ [backendAppId]: backendAppId }],
cacheLocation: 'localStorage'
});
Actually making a request:
authContext.acquireToken(backendAppId, (error, token) => {
// error handling etc omitted
fetch(backendUrl, { headers: { Authorization: `Bearer ${token}` } })
.then(response => response.json())
.then(console.log)
})
So since the Azure AD v1 endpoint is not fully standards-compliant, we have to do things in a slightly weird way.
In Postman:
Select OAuth 2.0 under Authorization
Click Get new access token
Select Implicit for Grant Type
Enter your app's reply URL as the Callback URL
Enter an authorization URL similar to this: https://login.microsoftonline.com/yourtenant.onmicrosoft.com/oauth2/authorize?resource=https%3A%2F%2Fgraph.microsoft.com
Enter your app's application id/client id as the Client Id
Leave the Scope and State empty
Click Request token
If you configured it correctly, you'll get a token and Postman will configure the authorization header for you.
Now about that authorization URL.
Make sure you specify either your AAD tenant id or a verified domain name instead of yourtenant.onmicrosoft.com.
Or you can use common if your app is multi-tenant.
The resource is the most important parameter (and non-standards-compliant).
It tells AAD what API you want an access token for.
In this case I requested a token for MS Graph API, which has a resource URI of https://graph.microsoft.com.
For your own APIs, you can use either their client id or App ID URI.
Here is a screenshot of my settings:

Instagram API returning error when requesting access_token on Windows Phone

I've been trying to integrate the Instagram API in my app, but am stuck with the authentication. I had it working completely fine when I was just using the implicit flow version which gave me the access_token as part of the URI fragment.
However, now I'm changing to the server-side flow, in which I receive a code after the user logs in. I then post this code to the access token URL, which will then give me the access_token as well as certain information about the user, such as their username and profile picture link.
I am using the InstaSharp library, modifying the source code.
HttpClient client = new HttpClient { BaseAddress = new Uri(config.OAuthUri + "access_token/", UriKind.Absolute) };
var request = new HttpRequestMessage(HttpMethod.Post, client.BaseAddress);
request.AddParameter("client_secret", config.ClientSecret);
request.AddParameter("client_id", config.ClientId);
request.AddParameter("grant_type", "authorization_code");
request.AddParameter("redirect_uri", config.RedirectUri);
request.AddParameter("code", code);
return client.ExecuteAsync<OAuthResponse>(request);
After creating my request, it is formatted as so:
{Method: POST, RequestUri: 'https://api.instagram.com/oauth/access_token/?client_secret={CLIENT_SECRET}&client_id={CLIENT_ID}&grant_type=authorization_code&redirect_uri=http://instagram.com &code={CODE}', Version: 1.1, Content: , Headers: { }}
(I inserted the space between the redirect_uri and code because it wouldn't let me post the question otherwise)
Everything appears normal in the address, but I always receive an error in the retuned json file:
"{"code": 400, "error_type": "OAuthException", "error_message": "You must provide a client_id"}"
I have no clue what is causing this error. Any help is greatly appreciated!
Thanks!
Elliott
Are you using the latest version of InstaSharp? Fork it here. You can check the README.md there although it's a bit outdated and you need to tweak some config. Here's how you can do it with the latest version that is in github:
// create the configuration in a place where it's more appropriate in your app
InstaSharpConfig = new InstagramConfig(
apiURI, oauthURI, clientId, clientSecret, redirectUri);
// then here's a sample method you can have to initiate auth
// and catch the redirect from Instagram
public ActionResult instagramauth(string code)
{
if (string.IsNullOrWhiteSpace(code))
{
var scopes = new List<InstaSharp.Auth.Scope>();
scopes.Add(InstaSharp.Auth.Scope.likes);
var link = InstaSharp.Auth.AuthLink(
oauthURI, clientId, redirectUri, scopes);
// where:
// oauthURI is https://api.instagram.com/oauth
// clientId is in your Instagram account
// redirectUri is the one you set in your Instagram account;
// for ex: http://yourdomain.com/instagramauth
return Redirect(link);
}
// add this code to the auth object
var auth = new InstaSharp.Auth(InstaSharpConfig);
// now we have to call back to instagram and include the code they gave us
// along with our client secret
var oauthResponse = auth.RequestToken(code);
// save oauthResponse in session or database, whatever suits your case
// oauthResponse contains the field Access_Token (self-explanatory),
// and "User" that'll give you the user's full name, id,
// profile pic and username
return RedirectToAction("action", "controller");
}
Take note that you can split up the "instagramauth" method. Did it that way for brevity.

Resources