OneDrive client authentication (SDK) - c#

I'm playing with OneDrive SDK 1.1.15.0:
try
{
AppConfig appConfig = new AppConfig
{
MicrosoftAccountAppId = oneDriveClientID, //something like 00000000123456AB
MicrosoftAccountClientSecret = oneDriveClientSecret, //something like 3vx[...]1sJ
MicrosoftAccountReturnUrl = "https://localhost/return",
MicrosoftAccountScopes = new string[] { "wl.signin", "wl.offline_access", "onedrive.readonly" }
};
OneDriveClient oneDriveClient = new OneDriveClient(appConfig);
AccountSession accountSession = await oneDriveClient.AuthenticateAsync();
//more code
await oneDriveClient.SignOutAsync();
}
catch (Exception ex)
{
throw ex;
}
My problem is in line:
AccountSession accountSession = await oneDriveClient.AuthenticateAsync();
that throws the following exception:
Microsoft.OneDrive.Sdk.OneDriveException, AuthenticationFailure: Failed to retrieve a valid authentication token for the user.
Any ideas?
Thank you in advance!
UPDATE
After reading comment from ginach (thank you!), I update my code. Some arguments to underline:
I want to access OneDrive from an Azure worker Role, so no authentication windows or something like that.
I upload the Microsoft.OneDrive SDK to 1.1.20 version.
I already registered my application to the OneDrive dev portal.
My actual code is:
try
{
MicrosoftAccountServiceInfo serviceInfo = new MicrosoftAccountServiceInfo();
serviceInfo.AppId = oneDriveClientID; //something like: 00000000ABCDEFGH
serviceInfo.ClientSecret = oneDriveClientSecret; //something like: 3vx[...]1sJ
serviceInfo.ReturnUrl = oneDriveReturnUrl; //something like: https://localhost/return
serviceInfo.Scopes = oneDriveAccountScopes; //something like new string[] { "wl.signin", "wl.offline_access", "onedrive.readonly" }
MicrosoftAccountAuthenticationProvider authenticationProvider = new MicrosoftAccountAuthenticationProvider(serviceInfo);
OneDriveClient oneDriveClient = await OneDriveClient.GetAuthenticatedMicrosoftAccountClient(oneDriveClientID, oneDriveReturnUrl, oneDriveAccountScopes, authenticationProvider);
//more code
await oneDriveClient.SignOutAsync();
}
catch (OneDriveException odex)
{
throw odex;
}
catch (Exception ex)
{
throw ex;
}
I obtain again and again (in OneDriveClient.GetAuthenticatedMicrosoftAccountClient method) a OneDriveException stating (Error property): AuthenticationFailure - Failed to retrieve a valid authentication token for the user.
Any suggestion?
Thank you.
UPDATE 2
OK, I'm trying a new approach. Using RestSharp I try to login to OneDrive with that code:
string clientId = "00[...]00";
string scopes = "wl.signin, wl.offline_access, onedrive.readonly";
string responseType = "code";
string redirectUri = "https://login.live.com/oauth20_desktop.srf";
RestClient client = new RestClient("https://login.live.com");
RestRequest request = new RestRequest();
request.Method = Method.GET;
request.Resource = "oauth20_authorize.srf";
request.AddQueryParameter("client_id", clientId);
request.AddQueryParameter("scope", scopes);
request.AddQueryParameter("response_type", responseType);
request.AddQueryParameter("redirect_uri", redirectUri);
IRestResponse response = client.Execute(request);
string content = response.Content;
I check the request with Fiddler and what I'm sending is:
https://login.live.com/oauth20_authorize.srf?client_id=00[...]00&scope=wl.signin%20wl.offline_access%20onedrive.readonly&response_type=code&redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf
But OneDrive server answers my with:
Microsoft account requires JavaScript to sign in. This web browser either does not support JavaScript, or scripts are being blocked. To find out whether your browser supports JavaScript, or to allow scripts, see the browser's online help.
So I try the request in a browser and OneDrive server redirects me to the authorization page:
Now the question is: is there any workaround to skip the manual authorization?
Thank you,
Attilio

The client requires an authentication provider to be able to retrieve authentication tokens. There are a few ways to do this depending on your current platform.
Create your own IAuthenticationProvider implementation. The authentication provider is responsible for setting the Authentication header on requests. Here's how you would create a client instance with a custom authentication provider:
var client = new OneDriveClient(appConfig, serviceInfoProvider: new
ServiceInfoProvider(new CustomAuthenticationProvider()));
Use one of the various default authentication implementations. Take a look at the SDK authentication documentation for the available options and examples.
If you have a refresh token and only want to do the silent authentication flow you can use OneDriveClient.GetSilentlyAuthenticatedMicrosoftAccountClient. Here's an example:
var client = await OneDriveClient.GetSilentlyAuthenticatedMicrosoftAccountClient(clientId, returnUrl, scopes, refreshToken);

Related

is there a way to associate a spotify access token to a ASP.NET identity user?

I'm working on a multilanguage project for accademic purpose. I've written a simple Python Client that make requests to an API server written in ASP.NET. The server retrives spotify info about users. The server interacts with a DB filled by a Golang server that only makes scraping on API's exposed from Spotify. I'm aware that it's a misuse and there are better solutions
Clearly, Golang server, in order to make requests to Spotify API's, needs to know the access token returned from spotify Authorization Code Flow. Overlooking about spotify token expire time, the idea is: after user authentication through Identity module of ASP.NET server (using JWT token), associate the access token obtained calling https://accounts.spotify.com/api/token to user's informations. So, i expose an API in ASP.NET server like this
[AllowAnonymous]
[HttpPost("token")]
public async Task<ContentResult> getTokenAsync(string? code = null)
{
//to retrive information about who is the user that making call -> need later for associate spotifytoken
string accessToken = Request.Headers[HeaderNames.Authorization].ToString().Replace("Bearer ", "");
JwtSecurityTokenHandler t = new JwtSecurityTokenHandler();
var token = t.ReadJwtToken(accessToken);
var user = _userManager.FindByIdAsync(token.Subject).Result;
string s = "https://accounts.spotify.com/api/token";
if (code == null)
{
var qb = new QueryBuilder();
qb.Add("response_type", "code");
qb.Add("client_id", _config["SpotiSetting:clientId"]);
qb.Add("scope", "user-read-private user-read-email user-library-read");
qb.Add("redirect_uri", _config["SpotiSetting:redirectUser"]);
qb.Add("show_dialog", "true");
return new ContentResult
{
ContentType = "text/html",
Content = "https://accounts.spotify.com/authorize/" + qb.ToQueryString().ToString()
//Content = JsonConvert.SerializeObject(user.Result)
};
} else
{
//if i'm here, api is the callback designed for spotify
var qb = new QueryBuilder();
qb.Add("grant_type", "authorization_code");
qb.Add("code", code);
qb.Add("redirect_uri", "https://localhost:44345/spotify/token");
var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Post, s);
req.Content = new FormUrlEncodedContent(qb);
req.Headers.Authorization = new AuthenticationHeaderValue("Basic", "here_my_secret_encoded_CLIENTID:CLIENT_SECRET");
var response = await client.SendAsync(req);
var result = response.Content.ReadAsStringAsync().Result;
AccessToken json = JsonConvert.DeserializeObject<AccessToken>(result);
user.spotifyInformation.authToken = code;
user.spotifyInformation.accessToken = json;
var res = _userManager.UpdateAsync(user);
if (res.IsCompletedSuccessfully)
{
return Content("ok");
}
else
{
Content("Problem");
}
} return Content("");
}
The problem is that the second time that API is invoked, it's spotify that is sending the first authorization token (needed to request access_token), so I lost user information retrived in the first request. Should be better write two distinct API and separate callback from user request?
It's my first question here, so please to have mercy

Error: Unauthenticated, Must be authenticated to use '/drive' syntax - When getting an item using rest api (graph)

I am using a windows application to upload and download a file from onedrive api.
Code to retrieve token (This code is directly downloaded from azure portal after creating an app registration)
string graphAPIEndpoint = "https://graph.microsoft.com/v1.0/me";
string[] scopes = new string[] { "user.read" };
AuthenticationResult authResult = null;
var app = App.PublicClientApp;
ResultText.Text = string.Empty;
TokenInfoText.Text = string.Empty;
var accounts = await app.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
try
{
authResult = await app.AcquireTokenSilent(scopes, firstAccount)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
authResult = await app.AcquireTokenInteractive(scopes)
.WithAccount(firstAccount)
.WithParentActivityOrWindow(new WindowInteropHelper(this).Handle) // optional, used to center the browser on the window
.WithPrompt(Prompt.SelectAccount)
.ExecuteAsync();
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
}
}
This is the code to get the download url of an item from onedrive api
string url="https://graph.microsoft.com/v1.0/me/drive/root:/Qwerty/test.txt";
string token=authResult.AccessToken;
var httpClient = new System.Net.Http.HttpClient();
System.Net.Http.HttpResponseMessage response;
try
{
var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
//Add the token in Authorization header
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
Response cls = new Response();
cls.Success = "TRUE";
cls.Method = "GetAllFiles";
cls.Data = content;
return cls;
}
else
{
Response cls = new Response();
cls.Success = "FALSE";
cls.Method = "GetAllFiles";
cls.Data = content;
return cls;
}
Im getting this error "Must be authenticated to use '/drive' syntax". this app works with one of my personal app registration . but when i use the below app registration its strating to show this error. i followed the exact same steps in creating the app registration i dnt knw why this error.
client id with error: 463921cd-72a3-495d-847e-259b99dda89e
Please help me
This is the sreenshot
If you would like to download the contents of a DriveItem, you must add one of the following permissions to call this API. And you need to add the delegated permission when using /me. The scope in your code is also changed with permission.
Then you could refer to the code sample:
C# with graph SDK: https://stackoverflow.com/a/63806689/13308381
Try to follow these steps in Postman:
1.Request an authorization code
GET https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?client_id={client_id}&scope={scope}
&response_type=code&redirect_uri={redirect_uri}
2.Request an access token
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
client_id={client_id}&scope={scope}&redirect_uri={redirect_uri}&client_secret={client_secret}
&code={code}&grant_type=authorization_code
3.Call Microsoft Graph API
GET /me/drive/root:/{item-path}:/content
I have found out the issue. issue was when creating an access token we need specify scopes like(Files.Read,Files.ReadWrite,etc (add whatever we need)) and then use the same token for downloading's a file and it was ok

Calling management.azure.com API fails due to authentication

I have a WEB API application working via Azure active directory.
I can get the information of all the user in active directory like this:
var app = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(_applicationOptions).Build();
string[] scopes = { "https://graph.microsoft.com/.default" };
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
}
catch (MsalServiceException ex)
{
// Case when ex.Message contains:
// AADSTS70011 Invalid scope. The scope has to be of the form "https://resourceUrl/.default"
// Mitigation: change the scope to be as expected
}
// use the default permissions assigned from within the Azure AD app registration portal
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.GetAsync("https://graph.microsoft.com/v1.0/users");
string content = await response.Content.ReadAsStringAsync();
But if I try to get tenants calling
https://management.azure.com/tenants?api-version=2019-06-01
I receive AuthenticationFailed error.
I guess this is because my AccessToken doesn't have the necessary scopes.
How can I fix it?
You are getting an access token for MS Graph API, not Azure Management API.
Use the following scope:
https://management.core.windows.net/.default
Docs:
https://learn.microsoft.com/en-us/rest/api/azure/#authorization-code-grant-interactive-clients

"BotAuthenticator failed to authenticate incoming request!" error in Teams when using Bot

I've created a Bot application in Visual Studio 2017, which I want to use in MS Teams. This application is part of a solution, which contains 2 components, the bot application itself and a windows application, which I have created that is used by the bot application to retrieve an authentication token from Microsoft (using similar code to what is on this website https://learn.microsoft.com/en-us/azure/active-directory/develop/guidedsetups/active-directory-uwp-v2).
When debugging the bot after hosting it locally, I'am able to use the bot successfully in Teams. There is no error. However, now that I have registered the bot with the Microsoft Bot Framework in Azure, I'm now having issues as Teams returns back the message "Sorry, my bot code is having an issue." In Azure I have a Bots Channels Registration entity, which in its settings points to a messaging endpoint that is https://.azurewebsites.net/api/messages. I also have a Apps Service. Now I have transferred the application id that I received when registering the bot with the Microsoft Bot Framework and have put this into the bot application in Visual Studio in the web.config file along with the app password.
After testing this in the Bot Framework Emulator I get "POST 401 directline.postActivity" and in the "Inspector-JSON" I get "BotAuthenticator failed to authenticate incoming request!". This is my first bot application so I'm lost as to what I have potentially missed out so does anyone have any idea what I could try?
So here's what I have in my RootDialog.cs file, which where the endpoint will hit when the bot is used.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
string userInfo = "";
AuthTokenDeploy tokenDeploy = new AuthTokenDeploy();
userInfo = await tokenDeploy.MsGraphUserInfo();
if(!userInfo.Equals(""))
{
// send webhook to end user system
await SendToEndpoint(context, activity, activity.Text,
userInfo);
}
}
AuthTokenDeploy is an instance of another class, which is where the function to obtain the access token along with the user information from Microsoft is held. So I created a string "userInfo", which then takes the value given by MsGraphUserInfo().
public async Task<string> MsGraphUserInfo()
{
AuthenticationResult authResult = null;
string Text = null;
try
{
authResult = await App.PublicClientApp.AcquireTokenSilentAsync(_scopes, App.PublicClientApp.Users.FirstOrDefault());
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync. This indicates you need to call AcquireTokenAsync to acquire a token
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
authResult = await App.PublicClientApp.AcquireTokenAsync(_scopes);
}
catch (MsalException msalex)
{
}
}
catch (Exception ex)
{
}
if (authResult != null)
{
Text = await GetHttpContentWithToken(_graphAPIEndpoint, authResult.AccessToken);
}
return Text;
}
Calling MsGraphUserInfo() will open the "AuthToken.exe" as a popup Window and ask the user to log in with their credentials. You can see from the code above that it acquires the access token first, which is then passed into GetHttpContentWithToken(), which is where a HTTP GET request is run against "https://graph.microsoft.com/v1.0/me" and a JSON string is returned with the user information in it.
public async Task<string> GetHttpContentWithToken(string url, string token)
{
var httpClient = new System.Net.Http.HttpClient();
System.Net.Http.HttpResponseMessage response;
try
{
var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
//Add the token in Authorization header
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
return content;
}
catch (Exception ex)
{
return ex.ToString();
}
}
Now I feel as if this method of using a Windows application to allow the user to log in might not be the best way forward, hence why I've been reading and following this guide https://learn.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-auth-aad. I would like to know whether it is possible to use what is on this page to allow my bot to retrieve an access token?

WebTokenRequest and OneDriveClient

I'm trying to implement Onedrive client login by using Connect to identity providers with Web Account Manager
With this method finally I get a token using this code
private static async Task<string> RequestTokenAndSaveAccount(WebAccountProvider Provider, String Scope, String ClientID)
{
try
{
WebTokenRequest webTokenRequest = new WebTokenRequest(Provider, "wl.signin onedrive.appfolder onedrive.readwrite", ClientID);
WebTokenRequestResult webTokenRequestResult = await WebAuthenticationCoreManager.RequestTokenAsync(webTokenRequest);
if (webTokenRequestResult.ResponseStatus == WebTokenRequestStatus.Success)
{
App.settings.onedriveStoredAccountKey = webTokenRequestResult.ResponseData[0].WebAccount.Id;
return webTokenRequestResult.ResponseData[0].Token;
}
return "";
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return "";
}
}
But I can't use the returned token to create a OnedriveClient because I need a MsaAuthenticationProvider to create the client and it creates its own token ignoring the one coming from the WebTokenRequest, and it doesn't have any method to take the prior token.
There is a way to create a OneDriveClient without going to REST Onedrive API?
Thank you
Edit:
As there are (at this time) two main versions of OneDriveSDK and those are different from each other, there are two ways to achieve this.
OneDrive.SDK 1.x
As #Brad said, an IAuthenticationProvider is needed to create the OneDriveClient.
I got the solution from https://github.com/ginach/Simple-IAuthenticationProvider-sample-for-OneDrive-SDK.
I took the SimpleAuthenticationProvider into my code, and then created the client like this
var client = new OneDriveClient(
new AppConfig(),
/* credentialCache */ null,
new Microsoft.OneDrive.Sdk.HttpProvider(),
new ServiceInfoProvider(new SimpleAuthenticationProvider { CurrentAccountSession = new Microsoft.OneDrive.Sdk.AccountSession { accessToken = AccessToken } }),
ClientType.Consumer);
client.BaseUrl = "https://api.onedrive.com/v1.0";
await client.AuthenticateAsync();
Where the accessToken is taken from the RequestTokenAndSaveAccount method.
OneDrive.SDK 2.x
For this case, the answer given by #dabox is the right solution.
Appending to Brad's answer, you can create a new AuthenticationProivder implements the IAuthenticationProivder interface in the package Microsoft.Graph.Core. And there also is a DelegateAuthenticationProvider in package Microsoft.Graph.Core which provides a Delegate interface for you. An example looks like:
OneDriveClient oneDriveClient = new OneDriveClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await GetAccessTokenSomeWhereAsync();
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
}));
return oneDriveClient ;
Modified based on the Microsoft Graph's asp .net example: https://github.com/microsoftgraph/aspnet-connect-sample/blob/master/Microsoft%20Graph%20SDK%20ASPNET%20Connect/Microsoft%20Graph%20SDK%20ASPNET%20Connect/Helpers/SDKHelper.cs#L18
OneDriveClient only requires an IAuthenticationProvider, which is a pretty simplistic interface. You can create your own and implement AuthenticateRequestAsync such that it calls your RequestTokenAndSaveAccount and then adds the bearer token to the request.

Categories

Resources