0

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

Nikolay
  • 10,752
  • 2
  • 23
  • 51
  • As far as I remember, calling sharepoint rest endpoint using app only authentication requires to use a certificate. Using a appsecret isn't allowed. – Steve B Mar 02 '21 at 09:31
  • I don't want to call SharePoint using app only, I want to call it on behalf of the user. Basically, trade the user token I get from teams for a token that is suitable for calling SharePoint REST API. Many articles are explaining how to do this for graph API (like the one in the description), but I have not found any that explains it for SharePoint REST API. And the tokens seem to be incompatible. Or maybe I am missing something? – Nikolay Mar 03 '21 at 10:44
  • Are you specifically using "https://microsoft.sharepoint-df.com/AllSites.Read" as your permission in the manifest? If so, you need to replace the domain with your target tenant domain. You may also need to go into the Azure Portal and see if your Teams app has had the target SharePoint permissions approved. – Jeremy Kelley - Microsoft Mar 24 '21 at 16:57
  • @JeremyKelley-Microsoft Thank you for the comment. Yes, I used that one. But it's a teams app that should work with any customer domain... I mean, I don't know ahead what the customer's domain is, so I can't add it to app registration? – Nikolay Mar 24 '21 at 22:04
  • @JeremyKelley-Microsoft I have tried putting "https://{tenant}/AllSites.Read" in the consent request, getting back "invalid_resource" error. I don't think I can put it to app registration itself? I mean, there is just a fixed set? – Nikolay Mar 24 '21 at 22:22
  • I have also tried without prefixes, like "AllSites.Read" - it's accepted (https://microsoft.sharepoint-df.com/AllSites.Read is also accepted) but the result is "invalid_issuer" when accessing sharepoint - exactly my problem. Is it even possible??? Maybe "SharePoint" (aka "AllSites.Read") grant is doing something else (like grating access to the https://microsoft.sharepoint-df.com site)? :D – Nikolay Mar 24 '21 at 22:33
  • Using microsoft.sharepoint-df.com is definitely incorrect, was there some sample docs that were using that URL as an example? – Jeremy Kelley - Microsoft Mar 26 '21 at 16:53
  • 1
    This may need input from the Teams team who knows the Teams app flow better. You might also try: "https://" + domain + "/.default" as your scope and then as long as the permissions you want are granted in AAD it may work. – Jeremy Kelley - Microsoft Mar 26 '21 at 16:56
  • @JeremyKelley-Microsoft The microsoft.sharepoint-df.com is added when you simply add permission in the sharepoint in AD portal, it's right in the manifest: https://i2.paste.pics/50c1220aa4c9730c8edec65589faa16c.png. I'll try with https://{domain}/.default – Nikolay Mar 27 '21 at 20:46
  • Dear Jeremy, this WORKED!!! I mean, calling it with "https://{tenant}/.default" as a scope! Could you please post this as an answer so that I can accept it for others to see how to solve the issue. Putting the "https://{tenant}/AllSites.Read" also worked actually, it was my mistake. You need to put it NOT in the manifest though (it is not possible to put it there), but in the GRANT CONSENT request and then in the on-behalf-flow. Thank you so much!!! – Nikolay Mar 27 '21 at 21:01

3 Answers3

1

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:

Configure SP Rest request

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

Steve B
  • 36,818
  • 21
  • 101
  • 174
  • This sounds like exactly the answer I was looking for. Thank you so much. I'll check it out. – Nikolay Mar 03 '21 at 13:08
  • As a side note, did you check the PnPJs library ? This may help to call REST api without dealing with plumbing – Steve B Mar 03 '21 at 13:18
  • Yes, it is a nice library, I have not just used but contributed to it. But this appears to be unrelated to authentication. – Nikolay Mar 03 '21 at 14:31
  • Does not help unfortunately. Maybe specific to my (single-sign-on) scenario. I already have user token, and want to exchange it for another one (access token) that would allow me to access SharePoint resources (for details please check the link in the description). The endpoint https://accounts.accesscontrol.windows.net/ seems to be useless for this, as it does not **exchange** tokens, but simply gets one? – Nikolay Mar 07 '21 at 22:50
1

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>

Nikolay
  • 10,752
  • 2
  • 23
  • 51
0

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.

enter image description here

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.

enter image description here enter image description here enter image description here

Tiny Wang
  • 10,423
  • 1
  • 11
  • 29
  • You understood it correctly. I'm just keep getting access denied. I thought that the token used to call graph API may not be suitable for calling SharePoint REST API directly, and that's the reason. By "graph api" (that works) I mean this: https://learn.microsoft.com/en-us/graph/api/resources/sharepoint?view=graph-rest-1.0 – Nikolay Mar 01 '21 at 12:16
  • comment on update => yes, this is correct; you need permission for graph API to be able to communicate to sharepoint sites using graph API. But it seems that graph token is valid for graph api only, and if I try to use it like in your first screenshot, I get access denied, and this is my problem. So the question in the description is - how to call sharepoint REST API directly, not through graph API. I mean, in your second example, you are using graph API, not sharepoint rest API. – Nikolay Mar 02 '21 at 16:40
  • To clarify - the "sharepoint part" of graph API is extremely limited, and it is not enough for my application. So, ideally I would like to get access to normal REST api. CSOM and server side could be also okay, I am basically looking for a way to trade "teams" token for "sharepoint" token (which is NOT "graph" token I belive. But maybe I am mistaking?) – Nikolay Mar 02 '21 at 16:47
  • Have you referred to this [doc](https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/complete-basic-operations-using-sharepoint-client-library-code) ? I assume it's what you want. – Tiny Wang Mar 03 '21 at 02:05
  • No it is not, but thank you for the efforts. – Nikolay Mar 03 '21 at 10:42
  • That's fine :D , and i'm glad to see you've got the answer. – Tiny Wang Mar 03 '21 at 13:39