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
Related
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
I am trying to get my mail on premises using GraphServiceClient. I am getting the token from Microsoft online account then use that token to get my emails. But because my mails are on premises I cannot access the as it throws the following error "code": "ResourceNotFound", "message": "Resource could not be discovered.". So how can I change my code to use the same token I get from online to on premises Mail using GraphServiceClient (if it is possible).
public async Task<ActionResult> Inbox()
{
string token = await GetAccessToken();
if (string.IsNullOrEmpty(token))
{
// If there's no token in the session, redirect to Home
return Redirect("/");
}
GraphServiceClient client = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
return Task.FromResult(0);
}));
var mailResults = await
client.Me.MailFolders.Inbox.Messages.Request()
.OrderBy("receivedDateTime DESC")
.Select("subject,receivedDateTime,from")
.Top(10)
.GetAsync();
return View(mailResults.CurrentPage);
}
public async Task<string> GetAccessToken()
{
string accessToken = null;
// Load the app config from web.config
string appId = ConfigurationManager.AppSettings["ida:AppId"];
string appPassword = ConfigurationManager.AppSettings["ida:AppPassword"];
string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
string[] scopes = ConfigurationManager.AppSettings["ida:AppScopes"]
.Replace(' ', ',').Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
// Get the current user's ID
string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
if (!string.IsNullOrEmpty(userId))
{
// Get the user's token cache
SessionTokenCache tokenCache = new SessionTokenCache(userId, HttpContext);
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId, redirectUri, new ClientCredential(appPassword), tokenCache.GetMsalCacheInstance(), null);
// Call AcquireTokenSilentAsync, which will return the cached
// access token if it has not expired. If it has expired, it will
// handle using the refresh token to get a new one.
IEnumerable<IAccount> accounts = await cca.GetAccountsAsync();
IAccount fisrtAccount = accounts.FirstOrDefault();
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes, fisrtAccount);
accessToken = result.AccessToken;
}
return accessToken;
}
By default you can't access any on-prem information with the Microsoft Graph. The only exception to that is a preview feature in Exchange 2016 that allows you to access Calendar, Mail and Contact items transparently.
This feature has a number of infrastructure pre-requisites so I suggest you connect with your infrastructure team first to validate those. Once those pre-requisites are in place, you should not have to change any code from the application's perspective to access the content. Keep in mind this is a preview feature, so not supported for production workloads.
I have implemented Multi tenant application using Azure Active Directory in Angular 4.After user logged into my application i'm able get user info.But user photo is not getting from the Active directory for that i have implemented Graph API like below snippet.
public Task<UserDto> getPhoto(TenantDto tenantDto)
{
var client = new HttpClient();
client.BaseAddress = new Uri(String.Format("https://graph.windows.net/{0}/users/{1}/thumbnailPhoto?api-version=1.6", tenantDto.tenantKey, tenantDto.email));
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("image/jpeg"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tenantDto.token);
HttpResponseMessage response = client.GetAsync("").Result;
if (response.IsSuccessStatusCode)
{
return null;
//Status status = response.Content.ReadAsAsync<Status>().Result;
//if (status.Code == 200)
// InBoundResponse = JsonConvert.DeserializeObject<InBoundCallResponse>(status.Data.ToString());
//return InBoundResponse;
}
else
{
return null;
}
}
Here tenantDto.token is nothing but a logged in user "token" While calling this Graph API i'm getting 401 (Unauthorized) error. I have tried all but no use.
I have changed Graph API setting s in Active Directory APP also like below attachment
Also i have tried like below code it's working only for single tenant
[Route("AdUserImage"), HttpGet]
public async Task<HttpResponseMessage> userImage()
{
var authContext = new AuthenticationContext("https://login.windows.net/sampletest.onmicrosoft.com/oauth2/token");
var credential = new ClientCredential(clientID, clientSecret);
ActiveDirectoryClient directoryClient = new ActiveDirectoryClient(serviceRoot, async () =>
{
var result = await authContext.AcquireTokenAsync("https://graph.windows.net/", credential);
return result.AccessToken;
});
var user = await directoryClient.Users.Where(x => x.UserPrincipalName == "balu#sampletest.onmicrosoft.com").ExecuteSingleAsync();
DataServiceStreamResponse photo = await user.ThumbnailPhoto.DownloadAsync();
using (MemoryStream s = new MemoryStream())
{
photo.Stream.CopyTo(s);
var encodedImage = Convert.ToBase64String(s.ToArray());
}
//string token = await HttpAppAuthenticationAsync();
Status status = new Status("OK");
status = new Status("Found", null, "User exists.");
return Request.CreateResponse(HttpStatusCode.OK, status, _jsonMediaTypeFormatter);
}
but i need to implement for Multi tenant app.
Any Answer Appreciated.
Thanks in Advance........!
Delegate-user token:
1 .Acquire the token via the implict flow:
https://login.microsoftonline.com/{tenant}/oauth2/authorize?response_type=token&client_id={clientId}&redirect_uri={redirect_uri}&resource=https%3A%2F%2Fgraph.windows.net&nonce={nonce}
2 .Call the Azure AD Graph
GET: https://graph.windows.net/{tenant}/me/thumbnailPhoto?api-version=1.6
Content-Type: image/jpeg
Application token:
1 .Acquire the token via the client credentials flow
POST:https://login.microsoftonline.com/{tenant}/oauth2/token
grant_type=client_credentials&client_id={client_id}&client_secret={client_secret}&resource=https%3A%2F%2Fgraph.windows.net
2 .Call the Azure AD Graph
GET:https://graph.windows.net/{tenant}/users/{upn}/thumbnailPhoto?api-version=1.6
Content-Type: image/jpeg
If you only to get the thumbnail photo of sign-in user for the multiple tenant, you should login-in with Azure AD first and acquire the access token for the delegate user and used that token to call Azure AD Graph REST. Difference between these two kinds of token, you can refer the links below:
Get access on behalf of a user
Get access without a user
I'm using Delegate-user token as per your explnation using below url
https://login.microsoftonline.com/{tenant}/oauth2/authorize?response_type=token&client_id={clientId}&redirect_uri={redirect_uri}&resource=https%3A%2F%2Fgraph.windows.net&nonce={nonce}
But still not able receiving but i'm able getting 200 status but token is not return.i have implemented like below
var client = new HttpClient();
client.BaseAddress = new Uri("https://login.microsoftonline.com/{TenantID}/oauth2/authorize?response_type=token&client_id={ClientID}&redirect_uri={ApplicationUrl}&resource=https%3A%2F%2Fgraph.windows.net&nonce=a9d7730c-79f3-4092-803a-07f346de2cdf");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
HttpResponseMessage response = client.GetAsync("").Result;
if (response.IsSuccessStatusCode)
{
}
else
{
//return null;
}
It's not return the token.it is returning html content in success block
I am creating demo app that consumes sharepoint REST API. I'm able to successfully retrieve Bearer token (by client ID and certificate). App is registered in Azure AD and all Sharepoint permissions there are checked. I am able to retrieve all lists for example, but I am not able to retrieve a file. The same query works in browser. I assume it is permission problem. Do I have to register this app in SP? https://tenant.sharepoint.com/IT/_layouts/15/appinv.aspx I tried it without success, but I think permissions in Azure AD should be sufficient. My GET query returns
{"error":{"code":"-2130575338, Microsoft.SharePoint.SPException","message":{"lang":"en-US","value":"The file /IT/vystupnidokumentydoc/filename.docx does not exist."}}}
Same query is OK in browser in user context.
REST call:
private async static Task DoStuffInOffice365(string token)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
client.DefaultRequestHeaders.Add("Accept", "application / json; odata = verbose");
string url;
url = "https://tenant.sharepoint.com/_api/web/GetFileByServerRelativeUrl('/IT/vystupnidokumentydoc/filename.docx')";
using (HttpResponseMessage response = await client.GetAsync(url))
{
var contents = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
Console.WriteLine("Fail!");
else
Console.WriteLine("OK.");
}
}
Get token:
private async static Task<string> GetAccessToken()
{
//authentication context
string authority = "https://login.windows.net/tenant.onmicrosoft.com/";
AuthenticationContext authenticationContext = new AuthenticationContext(authority, false);
var certPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
certPath = certPath.Substring(0, certPath.LastIndexOf('\\')) + $"\\{CERT_FILE}";
var certfile = System.IO.File.OpenRead(certPath);
var certificateBytes = new byte[certfile.Length];
certfile.Read(certificateBytes, 0, (int)certfile.Length);
var cert = new X509Certificate2(
certificateBytes,
PRIVATE_KEY_PASSWORD,
X509KeyStorageFlags.Exportable |
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet);
ClientAssertionCertificate cac = new ClientAssertionCertificate(CLIENT_ID, cert);
var authenticationResult = await authenticationContext.AcquireTokenAsync(P_URL, cac);
return token = authenticationResult.AccessToken;
}
Based on the error message, it seems the file was not found on the location provided. Please ensure the file exits on the site.
Do I have to register this app in SP?
If you were developing SharePoint add-in, yes. And you can refer here for the authentication/authorization for the SharePoint add-in.
If you were not developing an SharePoint add-ins, we also can use the Microsoft Graph-GetItem to get the item and download the drive item through this REST.
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);