Access OneDrive Personal via Microsoft Graph API - Token issues - azure

I am looking into using the Microsoft Graph API with personal OneDrive (logging in via a hotmail account and not an organisation account!).
I want to create a Microsoft Graph client instance and consume the Microsoft Graph API with use-cases such as this
var expandValue = this.clientType == ClientType.Consumer
? "thumbnails,children($expand=thumbnails)"
: "thumbnails,children";
folder = await this.graphClient.Me.Drive.Root.Request().Expand(expandValue).GetAsync();
To retrieve the access token I am using the code grant flow approach as described within this article.
The authorize endpoint being used
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
The token endpoint being used
https://login.microsoftonline.com/common/oauth2/v2.0/token
The scope being used
files.readwrite.all offline_access
Logging in with a hotmail account is returning a non JWT token
This is the first part of this token
EwBgA8l6BAAUO9chh8cJscQLmU+LSWpbnr0vmwwAAcGxJjYeNNkhw+sJQb2zJ
When trying to use this token to create a Microsoft Graph service client instance and consume requests such as the ones shown above, I am getting this error
Code: InvalidAuthenticationToken
Message: CompactToken parsing failed with error code: 8004920A
Inner error:
AdditionalData:
request-id: 3f472759-9718-47fb-8da0-df1646bb2fe8
date: 2020-05-19T16:42:44
ClientRequestId: 3f472759-9718-47fb-8da0-df1646bb2fe8
I tried setting the Azure AD App registration "signInAudience" parameter in the manifest to both "AzureADAndPersonalMicrosoftAccount" and "PersonalMicrosoftAccount". The former returns the token as shown above which then fails when sending the request, while when using the latter sign in audience, the token is simply not retrieved and a "Bad request" error with literally no extra info is returned.
Everything works fine when I login with my organisation account. It shows my personal folders within my organization's OneDrive for Business account.
Additionally, if I instead use the below line, I can view my organisation's OneDrive for business shared folders too (just by removing the .Me from after graphClient)
folder = await this.graphClient.Drive.Root.Request().Expand(expandValue).GetAsync();
This article supposedly describes retrieving a bearer token for Microsoft account cases, which I eventually presumed was what I needed.
The authorize endpoint being used
https://login.live.com/oauth20_authorize.srf
The token endpoint being used
https://login.live.com/oauth20_token.srf
The scope being used
onedrive.readwrite offline_access
Using this approach still returns the same type of token, so this literally left me within the same situation.
I also noticed that the approach within the latter article is discouraged and that in fact the approach of article 1 is suggested! So I simply wasted some more of my time :)
What kind of service/API has to be used for personal OneDrive access (via a hotmail account) ? What kind of token am I getting and what can I use it for? What could I be doing wrong here?

Related

Obtain SharePoint specific access token for a non-user application

I am working on a PHP web app that needs to make HTTP requests to the Sharepoint API with Sites.Selected permission to a specific SharePoint site. It is NOT viable for me to provide a user sign-in experience so I need to treat it as a non-user/daemon application.
I've read the docs and looked at many different forums for the solution but as of yet I've been unsuccessfull in obtaining a SPO specific access token, although I think I'm close.
I am using this StackOverflow answer as a guide: https://stackoverflow.com/a/63386756/19038862
This is what I've done:
Registered an Azure App: (Image of my Azure App Overview)
Created a client secret in the App dashboard: (Image of the client secret page)
Successfully sent a request to https://login.microsoftonline.com/{{app_tenant_id}}/oauth2/v2.0/token using the client secret in Postman: (Image of Postman request)
The request made in step 3 returns an access token (I assume a MS Graph access token?), but it DOES NOT return a refresh token, which is what the afforementioned StackOverflow answer suggests you need to "swap" for an SPO specific access token.
How do I obtain this refresh token so that I can swap it for a SPO access token? Or what better way is there to get my hands on a SPO specific access token from a non-user app?
I wrote this gist to guide you into getting Sites.Selected access to the desired site:
https://gist.github.com/ruanswanepoel/14fd1c97972cabf9ca3d6c0d9c5fc542
This guide shows you how to configure this as Application permissions, and via the Graph API.
I've found going through the Graph API is the best way to go.
Also strangely it's not possible to get delegated Sites.Selected permissions. You must set it up as an Application permission.
In the guide is described that you have to get a delegated auth token from graph but you are getting an application auth token. The token response of this flow does not contain a refresh_token. See here.
But you already wrote that you are not able to provide a user sign-in experience. One workaround would be to once manually get the access_token and refresh_token of a user with the delegated flow and then periodically get a new access_token with the refresh_token on your server. You could store these values in your database and update them when you fetch a new one.
First, the daemon-based client credential flow does not return a refresh token for you. You also can't redeem the refresh token of the graph API for an access token for SPO, which are two completely different API resources.
To get an access token for SPO you just need to set scope to: https://{tenant-name}.sharepoint.com/.default.

Call SharePoint REST API from Teams Tab

I have a Teams Tab application that needs to do some manipulations with the team's site.
The User needs to be authenticated, and all operations are executed on behalf of the user.
Calling the graph API is somewhat documented, I have found a good article here for example:
https://bob1german.com/2020/08/31/calling-microsoft-graph-from-your-teams-application-part3/
But I want to call SharePoint REST API directly, not through the graph API because I want to do some operations that are not supported by graph API (yet?), like creating a page.
How can I achieve this?
As far as I understand I need to exchange the token I get from teams to another token that can be used to call SharePoint. (on_behalf_of flow). I added the scopes for SharePoint to the app registration, and requesting those when exchanging the token (https://microsoft.sharepoint-df.com/AllSites.Read for example). But I keep getting 401 access denied.
Please note that this is NOT about calling graph API. This is about the "normal" SharePoint REST API. For calling graph API it works.
More details and REST calls:
https://gist.github.com/nbelyh/ec17a4e398069e35c2a2a5dc4447fb2a
I'm not sure if it matters regarding the "on behalf flow" vs "app only" flow, but from my experiments, aquiring tokens for graph call isn't same as acquiring token for SP rest call.
Specifically, endpoints aren't the same. Here's how I execute rest request from insomnia:
I guess the key is to use https://accounts.accesscontrol.windows.net/{{ tenantId }}/tokens/OAuth/2 instead of https://login.microsoftonline.com/{{ tenantId }}/oauth2/v2.0/token
Thank to #JeremyKelley-Microsoft for the answer, just posting it here for others:
You need to use https://{tenant}/AllSites.Read (or https://{tenant}/.default) as a scope, it DOES work. The {tenant} is the customer's tenant. Here is the flow:
0. Application registration permissions
1. get the token from teams
microsoftTeams.authentication.getAuthToken() => <teams_token>
2. trade for graph token (on-behalf-of flow)
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
client_id: <**your client id**>
client_secret: <**your client secret**>
grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer
assertion: <**teams_token**>
requested_token_use: on_behalf_of,
scope: https://{tenant}/AllSites.Read
=> returns the <access_token>
3. use access token to access sharepoint REST API (get root site)
GET https://{tenant}/_api/web
headers:
authorization: "bearer " + <access_token>
In my opinion, SharePoint api could be accessed via access token through a http request. So if you've achieved the feature of calling graph api, I think the operation is similar. First, create azure ad application and create client secret, then you need to add application according to the api you need to call, finally, using client credential flow or any other suitable flow to generate the access token.
Or you mentioned 'not through the graph API' means what I said above? If I misunderstand someplace, pls point it out, and I think it's better to tell us which api you'd like to call.
==========================UPDATE============================
According to the link you provided in the comment, I found the apis in it(e.g GetSite: https://graph.microsoft.com/v1.0/sites/root) requires the api permission of 'graph->Sites.ReadWrite.All'(they are all graph apis), so when you generate the access token, you need to add it in the scope, and of courese, you need to add the api permission first in azure portal. Then you could call the api.

Get a msgraph access token using another access token

I created an Azure app for which I created custom scopes through the "Expose API" screen. I have a single page application that uses the code flow to login into the application requesting these custom scopes. On my ASP.NET Core web application, I added the authentication layer to use JWT as bearer. Works pretty well and I can secure my web APIs as expected.
Now, I also added API permissions for msgraph because I want to be able to create online meetings with it. The flow would be:
The user logs in using my custom scope audience
He sends a call to a secure web api to create something
Something is added to the database
An online meeting is created on behalf of the user
The "issue" is that the access token received by my web API is not valid for msgraph, I need to get one, on behalf of the user related to the access_token. However, I have no idea how to get a msgraph token using another access_token. I don't even know if that's possible.
However, if it's not possible, how am I supposed to create the online meeting from the .NET Core part of my application ?
Take a look at the On Behalf Flow, specifically the example "First case: Access token request with a shared secret". You can use your existing access token as the assertion parameter.
The following HTTP POST requests an access token and refresh token with user.read scope for the https://graph.microsoft.com web API.
//line breaks for legibility only
POST /oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&client_id=2846f71b-a7a4-4987-bab3-760035b2f389
&client_secret=BYyVnAt56JpLwUcyo47XODd
&assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InowMzl6ZHNGdWl6cEJmQlZLMVRuMjVRSFlPMCJ9.eyJhdWQiOiIyODQ2ZjcxYi1hN2E0LTQ5ODctYmFiMy03NjAwMzViMmYzODkiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3YyLjAiLCJpYXQiOjE0OTM5MjA5MTYsIm5iZiI6MTQ5MzkyMDkxNiwiZXhwIjoxNDkzOTI0ODE2LCJhaW8iOiJBU1FBMi84REFBQUFnZm8vNk9CR0NaaFV2NjJ6MFFYSEZKR0VVYUIwRUlIV3NhcGducndMMnVrPSIsIm5hbWUiOiJOYXZ5YSBDYW51bWFsbGEiLCJvaWQiOiJkNWU5NzljNy0zZDJkLTQyYWYtOGYzMC03MjdkZDRjMmQzODMiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJuYWNhbnVtYUBtaWNyb3NvZnQuY29tIiwic3ViIjoiZ1Q5a1FMN2hXRUpUUGg1OWJlX1l5dVZNRDFOTEdiREJFWFRhbEQzU3FZYyIsInRpZCI6IjcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0NyIsInV0aSI6IjN5U3F4UHJweUVPd0ZsTWFFMU1PQUEiLCJ2ZXIiOiIyLjAifQ.TPPJSvpNCSCyUeIiKQoLMixN1-M-Y5U0QxtxVkpepjyoWNG0i49YFAJC6ADdCs5nJXr6f-ozIRuaiPzy29yRUOdSz_8KqG42luCyC1c951HyeDgqUJSz91Ku150D9kP5B9-2R-jgCerD_VVuxXUdkuPFEl3VEADC_1qkGBiIg0AyLLbz7DTMp5DvmbC09DhrQQiouHQGFSk2TPmksqHm3-b3RgeNM1rJmpLThis2ZWBEIPx662pjxL6NJDmV08cPVIcGX4KkFo54Z3rfwiYg4YssiUc4w-w3NJUBQhnzfTl4_Mtq2d7cVlul9uDzras091vFy32tWkrpa970UvdVfQ
&scope=https://graph.microsoft.com/user.read+offline_access
&requested_token_use=on_behalf_of

OAuth 2.0 {"error_description":"Invalid issuer or signature."} in Postman

I'm trying to execute a REST API call in SharePoint Online. For this, I wanted to see if I can register an app in Azure AD. I believe I was able to do so and I'm able to get back a token. However, upon executing the GET request Postman always throws {"error_description":"Invalid issuer or signature."}in the body of the response.
Here're the screenshots detailing everything:
Don't mind the Postman variable {{TenantID}}. That's not the issue, I also tried with the writte-out tenant ID - doesn't make a difference.
So what I was unsure about at first was the 'Scope' parameter in the "GET NEW ACCESS TOKEN" in Postman. I tried various scopes, for example
'Sites.FullControl.All' or 'https://microsoft.sharepoint-df.com/.default'. But that didn't change the outcome. Still, is the scope I set correct for SharePoint REST API? I know that for the Microsoft Graph 'https://graph.microsoft.com/.default' works.
I also tried different app permissions, not just
'Sites.FullControl.All'
Do you have any idea what the cause of the error might be?
Thanks.
The sharepoint permission Sites.FullControl.All is of type Application.
For this type of permission you MUST use the client_credential flow.
The Client Credentials grant is used when applications request an
access token to access their own resources, not on behalf of a user.
Try to call the auth url with:
grant_type=client_credentials
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx
Otherwise if you want to access the Sharepoint on behalf of the user change the permission type to delegated

Microsoft Graph API: how to get access token without browser

I would like to upload a given file to Sharepoint. I'm using the Microsoft Graph API.
The documentation follows this workflow:
1. If no token, redirect the user to the Microsoft signin page.
2. The user is then redirected to the application, with an access token
3. Use access token to have an authorization bearer
4. Do what you gotta do...
My problem is the sign-in part. I don't want my users to be redirected to the Microsoft signin page. I want my application to connect and get the access token in the background (with cURL or whatever).
How can I do that? Why is the "open in browser" necessary?
I tried to replicate the sign-in process, but all I get back is the HTML response from the signin page.
Thanks in advance.
Your application act as a single-tenant service or daemon app.
The documentation about this scenario is here : https://developer.microsoft.com/en-us/graph/docs/authorization/app_only
The application must be registered in the AzureAD directory corresponding to the Office365 tenant
A first request is made by passing the application unique identifier and secret key as registered in the directory. This request returns an access token
The access token can now be used in the Authorization header of the following request to the Microsoft Graph API.
This method (of using Client ID and Secret) works well but there are other ways which may be better suited for similar scenarios.
The one major thing which is missing in access token generated this way is a user, meaning the token only contains the identity of the OAuth application (client) which called it but is not associated with any user for the request.
This could have a couple of implications:
Since the token is not associated with a specific user you will not know who performed the operation. In your example, you would not know who uploaded the file (and other similar information may be missing).
Access token without users will not work at all for some methods. For those, you need a delegated token.
Creating a delegated token requires some effort, if you are interested you can find the details in my article:
Getting Access Token for Microsoft Graph Using OAuth REST API

Resources