4

I have an Angular application that lets users upload files. I am planning to store these files in the OneDrive using OneDrive API (the OneDrive account I have set up for the application).

I am aware that we have to use OAuth2.0 to get the access token from the webserver and use that token as a bearer token to use the API to manage my files in OneDrive.

How can I get this access token in my Angular app?

I need to get the access token without redirecting to the login page(Without interaction from the user). but in the background.

I tried the following URL to get the access token in POSTMAN.

https://login.microsoftonline.com/{Tenant_ID}/oauth2/token

I have tried,

  • Client credential flow.
  • Resource owner flow.
  • Implicit flow.

Function for Implicit flow in Angular (I have hardcoded the URL and values to test)

    getToken() {
    var msFormData = new FormData();
    msFormData.append('grant_type', 'client_credentials');
    msFormData.append('client_id', 'client_id');
    msFormData.append('client_secret', 'client_secret');
    msFormData.append('resource', 'https://graph.microsoft.com');

    return this.http.post("https://login.microsoftonline.com/{id}/oauth2/token", msFormData);
}

All three are working and able to get the token. When I tried implicit flow in Angular I am getting a CORS error. (I can't use implicit anyway because the client secret will be exposed).

When tried with the Resource_owner flow I got the SPO license error message. Where if I use the user flow and retrieve the access token from the redirect URL. I am able to use the Graph API with that access token to get the drive items. So I know I don't need an SPO license for this.(Maybe)

If that is not the best way I can create a service in my backend to get the access token/refresh token and serve it to the Angular app using an API, so the users can upload the files from the browser. But the access token got from the above flow gives me an SPO error.


UPDATE: I found out that to access the one drive we need a delegated access token. How is that different from client_credenttial flow? and how to get them?

Lenzman
  • 1,177
  • 18
  • 30
  • 1
    // You can use the example to silently request an access token and it works async getAccessToken(): Promise { let result = await this.msalService.acquireTokenSilent(OAuthSettings) .catch((reason) => { this.alertsService.addError('Get token failed', JSON.stringify(reason, null, 2)); }); if (result) { // Temporary to display token in an error box this.alertsService.addSuccess('Token acquired', result.accessToken); return result.accessToken; } // Couldn't get a token this.authenticated = false; return null; – Dev Apr 08 '21 at 01:08
  • BTW i used the code sample from [step-by-step Angular SPA example uses Microsoft Graph API and OneDrive API](https://learn.microsoft.com/en-us/graph/tutorials/angular). It worked for me. – Dev Apr 08 '21 at 01:15
  • But here we are passing only scopes. How about Id, Secret.? I want to upload the files to my one drive without showing the login form – Lenzman Apr 13 '21 at 06:20
  • If you need a delegation token, you cannot use the client credential flow. The client credential flow can only obtain the application token, that is, no user is logged in! – Carl Zhao Apr 20 '21 at 06:10
  • Is there a way to get the delegated token without interaction – Lenzman Apr 20 '21 at 06:26
  • When you say there is no user interaction, do you mean that there is no user participation? – Carl Zhao Apr 20 '21 at 14:24
  • the user has nothing to do with one drive. It is the applications's one drive account – Lenzman Apr 20 '21 at 14:33
  • To obtain the delegation token, the user must be logged in. Why don't you try application tokens? Then just call the `/user` endpoint. – Carl Zhao Apr 20 '21 at 14:49
  • to acess the one drive. This is the API given in documentation : https://graph.microsoft.com/v1.0/me/drive/root/children . To access this we need a get a token on behalf of a user. So I created a user and added as owner and set both delegated and application permissions for Files.Read.. Then I got a SPO licence error – Lenzman Apr 20 '21 at 14:59

4 Answers4

2

To get a valid graph token without user interaction you could use the following request. But be aware, this means in your client code somewhere the client secret is stored. And with this, the user could be able to request a valid application token and could access anything that is allowed in application scope.

So, like the name already states, the client secret should be kept secret and not be used in code on the client site. Better would be to have an own REST api, that sends the below request and returns the token to your angular application. How you secure this api is up to you.

Non-interactive user token

var settings = {
  "async": true,
  "crossDomain": true,
  "url": `https://login.microsoftonline.com/${tenantId}/oauth2/token`,
  "method": "POST",
  "headers": {
    "content-type": "application/x-www-form-urlencoded",
    "cache-control": "no-cache"
  },
  "data": {
    "client_id": `${clientId}`,
    "client_secret": ${clientSecret}``,
    "resource": "https://graph.microsoft.com/",
    "username": `${username}`,
    "password": `${password}`,
    "grant_type": "password",
    "scope": "openid"
  }
}

$.ajax(settings).done(function (response) {
  console.log(response);
});

Just though, you would be able to transform the above request to your concrete language. But as a special service I rewrote the above code to your given code (without testing):

getToken() {
    var msFormData = new FormData();
    msFormData.append('clientId', `${clientId}`);
    msFormData.append('client_secret', `${clientSecret}`);
    msFormData.append('resource', 'https://graph.microsoft.com');
    msFormData.append('username', `${username}`);
    msFormData.append('password', `${password}`);
    msFormData.append('grant_type', 'password');
    msFormData.append('scope', 'openid');
    
    return this.http.post(`https://login.microsoftonline.com/${tenantId}/oauth2/token`, msFormData);
}

Non-interactive application token

var settings = {
  "async": true,
  "crossDomain": true,
  "url": `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
  "method": "POST",
  "headers": {
    "content-type": "application/x-www-form-urlencoded",
    "cache-control": "no-cache",
  },
  "data": {
    "grant_type": "client_credentials",
    "scope": "https://graph.microsoft.com/.default",
    "client_id": `${clientId}`,
    "client_secret": `${clientSecret}`
  }
}

$.ajax(settings).done(function (response) {
  console.log(response);
});

Instead of using backtick strings, that only contain the variable, you could also use the variable itself directly. But I choosed this style to better highlight, that these parameters have to be provided by yourself.

Oliver
  • 43,366
  • 8
  • 94
  • 151
  • Thanks, I need to sign in as the application. So I tried with the app name as the username. which didn't work – Lenzman Apr 15 '21 at 08:59
  • 1
    @JeffinJ: For an application token the grant type must be different and a username or password is not needed. – Oliver Apr 15 '21 at 12:19
  • Yes, thanks for the updated answer. Is there any way we can overcome the CORS error on this request. I tried setting a Proxy. not working. – Lenzman Apr 15 '21 at 12:42
1

To upload a file on one drive in angular involves three steps,

  1. Authenticate end user against Azure AD and get id_token
  2. Get graph API token
  3. Call the file upload API for one drive

First step is when user Authenticate with Azure AD and this will happen interactively. We can use MSAL library to get the id token. Link mentioned below has all the documentation for that,

https://learn.microsoft.com/en-us/graph/auth-v2-user#1-register-your-app

Before second step in in Azure AD App we need map configure protected resource map and add graph API url over there with scope as "Files.ReadWrite.All". And then try to get the token for graph api token in the background with 3rd step mentioned in the link mentioned above. Graph API token has been generated using client secrete.

Once we receive the Graph API token then we can send the file. For that you can use the links mentioned below,

how to specify the stream to upload a file to onedrive using ms graph

  • Thanks for the answer. But there are few things to consider. The end-user doesn't have an MS account. I need to store the files to my account I have created in the name of my application. So login (interactively) with the login page is not an option. I have tried both client_credential and Resource owner flow and got the tokens. With RO flow, when I used the access token I got the users from the AD but when accessed `/me` for the drive I got no SPO license error. – Lenzman Apr 19 '21 at 10:36
  • 1
    In Azure AD we have a feature "Azure AD B2C" which allows end user having non MS account to get authenticate and authorize against Azure AD. So if we configure that core issue will get resolved. Please use link mentioned below for the same : [link](https://medium.com/@sambowenhughes/how-to-setup-active-directory-b2c-in-microsoft-azure-766f301e28e) – Tejas Mahajan Apr 19 '21 at 14:01
  • So I should create a new AAD B2c directory. Then register an application and use resource owner flow to get the access token? – Lenzman Apr 19 '21 at 14:03
  • We can't create an Azure AD B2C without a subscription and I have signedup for a subscription and did as you told. Still getting the error that `Tenant does not have a SPO license.` – Lenzman Apr 20 '21 at 07:29
  • Yes I have looked for that error on MSDN but did not got any proper answer. But one point I have noticed onedrive is part of office 365 and we might need subscription for office365 to tenant [link](https://stackoverflow.com/questions/46802055/tenant-does-not-have-a-spo-license#:~:text=All%20you%20need%20to%20do,AAD%20account%20you%20just%20created.) – Tejas Mahajan Apr 20 '21 at 08:20
  • If subscription is needed, Please use this [URL](https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id={tenant_id}&response_type=token&redirect_uri=https://localhost:4200&scope=Files.Read Files.Read.All Files.ReadWrite Files.ReadWrite.All) and place a tenant_id and load in your browser. You will get an access token returned to the localhost. You can see that in the same URL field. If you use that token to access One drive. You can.So hows that possible without subscription – Lenzman Apr 20 '21 at 08:26
1

The delegated token is generally called a user token, and the token you get through the client credential flow is an application token. The only difference between them is whether there is a user logged in.

If you want to obtain a delegated token, must log in user. Whether it is an interactive login or a non-interactive login, the user must be logged in!

In addition, you need change the /tenant id endpoint to the /common endpoint to avoid tenant-level login.

Carl Zhao
  • 8,543
  • 2
  • 11
  • 19
  • I have tried the non interactive flow [ropc](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc) But while using the access token to get the one drive I am getting this error: `Tenant does not have a SPO license` – Lenzman Apr 20 '21 at 15:05
  • @JeffinJ Is your account a personal account or a work account? – Carl Zhao Apr 21 '21 at 08:07
  • Personal account – Lenzman Apr 21 '21 at 08:37
  • @JeffinJ Personal accounts cannot use ROPC flow! – Carl Zhao Apr 21 '21 at 08:42
  • 1
    @JeffinJ There is a special note in the document that ropc flow does not support personal accounts. You can only use the auth code flow to get the delegation token. – Carl Zhao Apr 21 '21 at 08:45
  • I even tried auth code flow. 1. got the code from /authorize end point. 2.Used that code to get the access token and refresh toke. My plan was to do this in the initial setup and store the refresh token and use it to renew the access token. But while using the access token, got the same error – Lenzman Apr 21 '21 at 08:47
  • 1
    @JeffinJ Try to change the `/tenant id` endpoint to the `/common` endpoint to avoid tenant-level login. – Carl Zhao Apr 21 '21 at 08:56
  • 1
    @JeffinJ I found a similar problem, which is indeed the same as I thought, that is, you can only use auth code flow. see: https://stackoverflow.com/questions/66787193/onedrive-api-and-azure-active-directory-setup-to-upload-as-personal-account/66792238#66792238 – Carl Zhao Apr 21 '21 at 09:00
  • @JeffinJ Yes, use refresh tokens no need to interact with users . – Carl Zhao Apr 21 '21 at 10:06
  • Just to make sure something. with `\common` any account can log in right? where `/{tenant_id}` only the users in the tenant can log in right?. And do you know why we get the `Tenant does not have an SPO license` error if `/{tenant_id}` is used? even if we use `Auth code flow` – Lenzman Apr 21 '21 at 10:54
1

In case anyone else has a similar issue, a few weeks ago, when I was trying to do the same thing as OP in my angular application. As the company that I work on uses a personal Microsoft account, it was not possible to use non-interactive flows[1][2] to access the company one drive. So as the company employees already have the company one drive as a shared folder, we were finding a new login solution, and we only can use interactive ways to get access to the Graph API. We embrace MSAL and change our login method to use the employee's personal Microsoft account.

So to log in, we use @azure/msal-angular that also provides an HTTP interceptor and the popup strategy. Thus the employee fills their Microsoft credentials in the popup using an Oauth2 Token Flow. Once logged in, the MSAL_INTECEPTOR refreshes our token and puts the authorization code when making a request to Graph API. The changes in our code are described in these 2 commits[3][4]. With this part ready, now we can make a request to Onedrive API to upload files to the company shared folder that all employees have. I shared some tips about upload to shared folders here: Microsoft Graph: Uploading files to shared with me folder on OneDrive?.

I really liked the results with MSAL that I have, and the company employees also loved the direct Onedrive integration. Because they just need to login into our application one time and it's all set.

I shared a step by step Postman Oauth2 Code Flow to exercise the Onedrive file upload to personal accounts here: Tenant does not have a SPO license

Augusto Icaro
  • 553
  • 5
  • 15