I am currently developing a ASP.NET MVC 5 site which uses the Microsoft Graph API application to retrieve and insert data into Microsoft Planner. Said site already has Azure Active Directory authentication. I am currently using the following code to get the access token to login into the Graph API application.
public async Task<ActionResult> SignIn()
{
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
string redirectUri = Url.Action("Authorize", "Planner", null, Request.Url.Scheme);
Uri authUri = await authContext.GetAuthorizationRequestUrlAsync("https://graph.microsoft.com/", SettingsHelper.ClientId,
new Uri(redirectUri), UserIdentifier.AnyUser, null);
// Redirect the browser to the Azure signin page
return Redirect(authUri.ToString());
}
public async Task<ActionResult> Authorize()
{
// Get the 'code' parameter from the Azure redirect
string authCode = Request.Params["code"];
// The same url we specified in the auth code request
string redirectUri = Url.Action("Authorize", "Planner", null, Request.Url.Scheme);
// Use client ID and secret to establish app identity
ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
TokenCache fileTokenCache = new FilesBasedAdalV3TokenCache("C:\\temp\\justin.bin");
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.AzureADAuthorityTenantID, fileTokenCache);
AuthenticationResult authResult = null;
try
{
// Get the token silently first
authResult = await authContext.AcquireTokenAsync(SettingsHelper.O365UnifiedResource, credential);
}
catch (AdalException ex)
{
authContext = new AuthenticationContext(SettingsHelper.AzureADAuthority, fileTokenCache);
authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
authCode, new Uri(redirectUri), credential, SettingsHelper.O365UnifiedResource);
return Content(string.Format("ERROR retrieving token: {0}", ex.Message));
}
finally
{
// Save the token in the session
Session["access_token"] = authResult.AccessToken;
}
return Redirect(Url.Action("Index", "Planner", null, Request.Url.Scheme));
}
The code above gets the access token without any issue. I am able to get all users of the active directory without any issue and store them in a database. However when I try to get any data relating to a task I keep on getting the following error
{
StatusCode:401,
ReasonPhrase:'Unauthorized',
Version:1.1,
Content:System.Net.Http.StreamContent,
Headers:{
Transfer-Encoding: chunked request-id:40 b53d20-c4fc-4614-837b-57a6bebb8d79 client-request-id:40 b53d20-c4fc-4614-837b-57a6bebb8d79 x-ms-ags-diagnostic:{
"ServerInfo":{
"DataCenter":"North Europe",
"Slice":"SliceC",
"Ring":"2",
"ScaleUnit":"000",
"Host":"AGSFE_IN_17",
"ADSiteName":"NEU"
}
} Duration:28.4537 Strict-Transport-Security: max-age=31536000 Cache-Control: private Date:Fri,
07 Dec 2018 14:12:50 GMT Content-Type:application/json
}
}
I have checked azure app and it has full access rights. Any Help on this would be greatly appreciated
I have a managed to solve my issue. The issue was with Graph Api requiring you to run as delegated account as well as setting the App on azure as a native application.
The Code that was used is as follows
private async Task<string> GetAccessToken(string resourceId, string userName, string password)
{
try
{
var authority = ConfigurationManager.AppSettings["ida:AuthorizationLoginUri"] + ConfigurationManager.AppSettings["ida:TenantId"];
var authContext = new AuthenticationContext(authority);
var credentials = new UserPasswordCredential(userName, password);
var authResult = await authContext.AcquireTokenAsync(resourceId, ConfigurationManager.AppSettings["ida:ClientIdNativeClient"], credentials);
// Get the result
return authResult.AccessToken;
}
catch (Exception ex)
{
// TODO: handle the exception
return;
}
}
I had found this site https://karinebosch.wordpress.com/2017/12/18/microsoft-graph/ that encountered the same issue as me
Related
I want to implement authentication and authorization using Microsoft.Identity.Client in razor pages
I want to sign In users and logout also with the help of Microsoft office 365
For login I am getting access toke from code and authorizing as shown below
public async Task<IActionResult> OnGetAsync(string code)
{
string clientId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string tenantId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string clientSecret = "XXXXXXXXXXXXXXXXXX";
var httpClient = new HttpClient();
var app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.WithRedirectUri("http://localhost:5000/login")
.Build();
var scopes = new string[]
{
"User.Read",
"email",
"offline_access",
"openid",
"profile"
};
try
{
AuthenticationResult result = await app
.AcquireTokenByAuthorizationCode(scopes, code)
.ExecuteAsync();
string identifier = result.Account.HomeAccountId.Identifier;
Email = result.Account.Username;
accesstoken = result.AccessToken;
Console.WriteLine(result.UniqueId);
var graphQlInformation = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/beta/me");
graphQlInformation.Headers.Add("Authorization", "Bearer " + accesstoken);
var information = httpClient.SendAsync(graphQlInformation).Result;
var info = information.Content.ReadAsStringAsync().Result;
JObject json = JObject.Parse(info);
if (information.IsSuccessStatusCode)
{
Console.WriteLine("authorized");
Name = json["mailNickname"].ToString();
Console.WriteLine(json["mailNickname"].ToString());
}
else
{
Console.WriteLine("Un authorized");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return Page();
}
Wit this, I can able to get access token, but I want to keep user session like a cookie, means once a user is signed in,
if he closes browser and open it again, he shouldn't be asked to sign In again
I am following this article until now for authentication and authorization and change some implementation
So, I have 2 questions here
how can I sign in like a session with Oauth 2.0
Thw way I am following for authentication and authorization is right or not.
Please help, and please ask if any more clarification is needed, thanks
I am developing a daemon application. I have downloaded the sample application to try to see if it would work, but get the same error from that app too. My admin has checked as much as possible on his end, revealing nothing. The desired end result is to have a program that can send emails on behalf of users and read mail from certain mailboxes. I also need to be able to confirm in a quick way that my application is configured correctly.
This program will not have user interactivity and will be operating on behalf of the company.
What I've done:
Created app registration at aad.portal.azure.com.
Created a client secret.
Admin has given consent for 11 Microsoft Graph permissions: Mail.Read (Application), Mail.ReadBasic (Application), Mail.ReadBasic.All (Application), Mail.ReadWrite (Application), Mail.Send (Application), User.Export.All (Application), User.Invite.All (Application), User.ManageIdentities.All (Application), User.Read (Delegated), User.Read.All (Application), User.ReadWrite.All (Application)
Integration Assistant (preview) shows all green except for 1 item, which is "Use certificate credentials instead of password credentials (client secrets).". This particular one is acceptable to me.
On the Authentication page, this is not treated as a public client.
Nuget packages used:
Microsoft.Identity.Client 4.13.0
Microsoft.Graph 3.5.0
My sandbox code:
async void Main()
{
var graphFacade = new MsGraphFacade();
Console.WriteLine(await graphFacade.ValidateCredentialsAsync());
}
class MsGraphFacade
{
private static async Task<GraphServiceClient> GetGraphApiClient()
{
var clientId = "(Redacted)";
var secret = "(Redacted)";
var app = ConfidentialClientApplicationBuilder
.CreateWithApplicationOptions(new ConfidentialClientApplicationOptions{
ClientId = clientId,
ClientSecret = secret,
AadAuthorityAudience = AadAuthorityAudience.AzureAdMultipleOrgs,
})
.Build();
Console.WriteLine("Getting token");
var token = await app
.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
.ExecuteAsync();
Console.WriteLine("Got token");
var accessToken = token.AccessToken;
var graphServiceClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.CompletedTask;
}));
Console.WriteLine("New client returned.");
return graphServiceClient;
}
public async Task<bool> ValidateCredentialsAsync()
{
try
{
Console.WriteLine("Attempting something simple");
var client = await GetGraphApiClient();
var user = await client.Users
.Request()
.Top(1)
.Select(x => x.DisplayName)
.GetAsync();
if (user != null)
{
return true;
}
return false;
}
catch (Exception e)
{
Console.WriteLine("2");
Console.WriteLine(e);
return false;
}
}
}
Output of code:
Attempting something simple
Getting token
Got token
New client returned.
2
Code: Authorization_IdentityNotFound Message: The identity of the calling application could not be established.
Inner error:
AdditionalData:
request-id: f31bc340-1cdf-485f-b852-f1e2822201ef
date: 2020-05-15T20:24:38
False
Any ideas of what to check next or tweak will be greatly appreciated.
Thanks in advance for any help.
I'm guessing the issue is you have not specified the target tenant.
You've defined it like this:
AadAuthorityAudience = AadAuthorityAudience.AzureAdMultipleOrgs
You need to instead specify Azure public cloud + tenant guid. I'm on my phone right now so I can't look up the exact syntax :/
I try to get an access token for an identity to get data from all users profiles. I'm using OpenID connect to authenticate the user, in which I succeeded. I'm also able to get an access token, but it is not valid.
The code I'm using:
To authenticate:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
ClientId = AppVar.ClientId,
ClientSecret = AppVar.ClientSecret,
Authority = AppVar.AzureADAuthority,
RedirectUri = "https://localhost:44326/",
ResponseType = "code id_token",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = (context) => {
var code = context.Code;
ClientCredential credential = new ClientCredential(AppVar.ClientId, AppVar.ClientSecret);
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
ADALTokenCache cache = new ADALTokenCache(signedInUserID);
AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantID), cache);
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, AppVar.AzureResource);
return Task.FromResult(0);
}
}
});
To acquire an access token for https://graph.microsoft.com
public ActionResult Index()
{
string usrObjectId = ClaimsPrincipal.Current.FindFirst(AppVar.ClaimTypeObjectIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(AppVar.AzureADAuthority, new ADALTokenCache(usrObjectId));
ClientCredential credential = new ClientCredential(AppVar.ClientId, AppVar.ClientSecret);
AuthenticationResult res = authContext.AcquireToken(AppVar.AzureResource, credential);
var client = new RestClient("https://graph.microsoft.com/v1.0/users/?$select=userPrincipalName,displayName,mobilePhone");
var request = new RestRequest(Method.GET);
request.AddHeader("Cache-Control", "no-cache");
request.AddHeader("Authorization", "Bearer " + res.AccessToken);
IRestResponse response = client.Execute(request);
return View();
}
But when I execute the request, I get:
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError": {
"request-id": "1cc9e532-bd31-4ca5-8f1d-2d0796883c2e",
"date": "2018-10-17T06:50:35"
}
}
}
What am I doing wrong?
I had the same issue. Use following code which I have used to get the Access Token from Azure AD. Just Login to your Azure portal and find your Tenant ID and Client ID and paste it to the following code. It works perfectly for me.
namespace TokenGenerator
{
class Program
{
private static string token = string.Empty;
static void Main(string[] args)
{
//Get an authentication access token
token = GetToken();
}
#region Get an authentication access token
private static string GetToken()
{
// TODO: Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.21.301221612
// and add using Microsoft.IdentityModel.Clients.ActiveDirectory
//The client id that Azure AD created when you registered your client app.
string clientID = "Your client ID";
string AuthEndPoint = "https://login.microsoftonline.com/{0}/oauth2/token";
string TenantId = "Your Tenant ID";
//RedirectUri you used when you register your app.
//For a client app, a redirect uri gives Azure AD more details on the application that it will authenticate.
// You can use this redirect uri for your client app
string redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient";
//Resource Uri for Power BI API
string resourceUri = "https://analysis.windows.net/powerbi/api";
//Get access token:
// To call a Power BI REST operation, create an instance of AuthenticationContext and call AcquireToken
// AuthenticationContext is part of the Active Directory Authentication Library NuGet package
// To install the Active Directory Authentication Library NuGet package in Visual Studio,
// run "Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory" from the nuget Package Manager Console.
// AcquireToken will acquire an Azure access token
// Call AcquireToken to get an Azure token from Azure Active Directory token issuance endpoint
string authority = string.Format(CultureInfo.InvariantCulture, AuthEndPoint, TenantId);
AuthenticationContext authContext = new AuthenticationContext(authority);
string token = authContext.AcquireTokenAsync(resourceUri, clientID, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto)).Result.AccessToken;
Console.WriteLine(token);
Console.ReadLine();
return token;
}
#endregion
}
}
Looking at your error, since it's failing at token validation my guess would be that it's related to audience for which the token was acquired.
You're calling the https://graph.microsoft.com endpoint, so make sure that is the exact value for the resource.
Specifically in this code, make sure AppVar.AzureResource gets a value https://graph.microsoft.com
AuthenticationResult res = authContext.AcquireToken(AppVar.AzureResource, credential);
var client = new RestClient("https://graph.microsoft.com/v1.0/users/?$select=userPrincipalName,displayName,mobilePhone");
var request = new RestRequest(Method.GET);
Hello I have developed a Microsoft application using Microsoft Graph API in order to obtain planner data and store it in a database for now. On it's own the application works fine without any issue what so ever.
The next task for me is to integrate this separate application into the main company application. The main company's website uses form authentication. What is the best way to integrate this. Currently when I try to login to get authorized I am redirected to the form login not the Microsoft one
I have registered the application in the Microsoft application registration pool. I have also added the office 365 api
This is the token obtain code that i am using
public async Task<string> GetUserAccessTokenAsync()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
tokenCache = new SessionTokenCache(
signedInUserID,
HttpContext.Current.GetOwinContext().Environment["System.Web.HttpContextBase"] as HttpContextBase);
//var cachedItems = tokenCache.ReadItems(appId); // see what's in the cache
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId,
redirectUri,
new ClientCredential(appSecret),
tokenCache);
try
{
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes.Split(new char[] { ' ' }));
return result.Token;
}
// Unable to retrieve the access token silently.
catch (MsalSilentTokenAcquisitionException)
{
HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
throw new Exception(Resource.Error_AuthChallengeNeeded);
}
}
This is the sign in method I am trying use when trying to directly log in
// Signal OWIN to send an authorization request to Azure.
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
I have solved this issue by implementing the following code
public ActionResult SignIn()
{
var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
string redirectUri = Url.Action("Authorize", "Planner", null, Request.Url.Scheme);
Uri authUri = authContext.GetAuthorizationRequestURL("https://graph.microsoft.com/", SettingsHelper.ClientId,
new Uri(redirectUri), UserIdentifier.AnyUser, null);
// Redirect the browser to the Azure signin page
return Redirect(authUri.ToString());
}
public async Task<ActionResult> Authorize()
{
// Get the 'code' parameter from the Azure redirect
string authCode = Request.Params["code"];
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.AzureADAuthority);
// The same url we specified in the auth code request
string redirectUri = Url.Action("Authorize", "Planner", null, Request.Url.Scheme);
// Use client ID and secret to establish app identity
ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
try
{
// Get the token
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
authCode, new Uri(redirectUri), credential, SettingsHelper.O365UnifiedResource);
// Save the token in the session
Session["access_token"] = authResult.AccessToken;
return Redirect(Url.Action("Index", "Planner", null, Request.Url.Scheme));
}
catch (AdalException ex)
{
return Content(string.Format("ERROR retrieving token: {0}", ex.Message));
}
}
A link to the solution that helped tackle this was this. It's slightly old but still helped out massively
https://www.vrdmn.com/2015/05/using-office-365-unified-api-in-aspnet.html
I would like to be programmatically able to get a token from Azure.
I call GetAToken().Wait(); and it fails.
and the method is:
public async Task<string> GetAToken()
{
// authentication parameters
string clientID = "*********";
string username = "<azure login>";
string password = "<azure login password>";
string directoryName = "<AD Domain name>";
ClientCredential cc = new ClientCredential(clientID, password);
var authenticationContext = new AuthenticationContext(
"https://login.windows.net/" + directoryName);
AuthenticationResult result = await authenticationContext.AcquireTokenAsync(
"https://management.core.windows.net/", cc);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}
So not sure if you are doing this on Android, iOS or Xamarin.Forms. Below is how I will authenticate with ADAL and Azure (the code is working on my end):
On Android:
public async Task<AuthenticationResult> Authenticate(Activity context, string authority, string resource, string clientId, string returnUri)
{
var authContext = new AuthenticationContext(authority);
if (authContext.TokenCache.ReadItems().Any())
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
var uri = new Uri(returnUri);
var platformParams = new PlatformParameters(context);
try
{
var authResult = await authContext.AcquireTokenAsync(resource, clientId, uri, platformParams);
return authResult;
}
catch (AdalException e)
{
return null;
}
}
On iOS:
public async Task<AuthenticationResult> Authenticate(UIViewController controller, string authority, string resource, string clientId, string returnUri)
{
var authContext = new AuthenticationContext(authority);
if (authContext.TokenCache.ReadItems().Any())
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
var controller = UIApplication.SharedApplication.KeyWindow.RootViewController;
var uri = new Uri(returnUri);
var platformParams = new PlatformParameters(controller);
try
{
var authResult = await authContext.AcquireTokenAsync(resource, clientId, uri, platformParams);
return authResult;
}
catch (AdalException e)
{
return null;
}
}
On UWP:
public async Task<AuthenticationResult> Authenticate(string authority, string resource, string clientId, string returnUri)
{
var authContext = new AuthenticationContext(authority);
if (authContext.TokenCache.ReadItems().Any())
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
var uri = new Uri(returnUri);
var platformParams = new PlatformParameters(PromptBehavior.Auto);
try
{
var authResult = await authContext.AcquireTokenAsync(resource, clientId, uri, platformParams);
return authResult;
}
catch (AdalException e)
{
return null;
}
}
Variable that I pass into the methods above:
string authority = "https://login.windows.net/common";
string ResourceID = "Backend ClientId";//Backend (web app)
string clientId = "Native App ClientId";//native app
string returnUri = "https://{My Azure Site}.azurewebsites.net/.auth/login/done";
If you want to do this in Xamarin.Forms, below are links to my GitHub solution where I have exposed these methods through the DependencyService.
PCL implementation
iOS implementation
Android Implementation
I hope this helps! If you get any errors from your response, check to make sure you have your permissions setup in Azure correctly. I do it like this. Another great resource is Adrian Hall's Xamarin/Azure book
EDIT: Added UWP stuff
If what you are trying to do is call the Azure APIs as you, there are a few things you should do differently.
Create an app in Azure AD that has permissions to access the Azure API
If you want to call Service Management API, then add that as a permission
You could also alternatively use a management certificate
If you want to call Resource Management API, then add the permissions needed to the service principal through the new Portal
If you chose the delegated way for Service Management API (the first option), then you will have to either:
Have the user authenticate against Azure AD with the Authorization Code Grant flow
Or get the access token using the Password grant flow (you can see an example of this in another answer
If instead you chose a management certificate or giving the permissions to the service principal, then you can get the access token directly from Azure AD using the Client credentials grant flow
In the end you will always end up with an access token that you can use for calling the API.
IF you're using the wrappers, ensure to have the correct version-Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.21.301221612.
Once referenced, you can run this below. For alternatives, see this blog: https://samtran.me/2018/11/11/power-bi-rest-api/
If you are also running into issue on Android where device rotation returns you back to prompt for user email, you can follow up progress of fixes for both ADAL and MSAL here:
https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/issues/1622
https://github.com/xamarin/xamarin-android/issues/3326