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);
Related
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
How does one authenticate as a user without any direct user interaction?
Otherwise i found a workaround with client credential flow in this example : https://github.com/microsoftgraph/console-csharp-snippets-sample
but if i try to implement this code in an c# Asp.net mav applcition or a windows forms application i cant get an application token. If i debug the app it stuck at waiting for token but it doesn't throw an error (virus protection already deactivated). Does anyone have a idea for the main problem or my workaround?
This is the Code for my workaround trying to get a token, but stuck on daemonClient.AcquireTokenForClientAsync.
public async Task<Users> GetUser(string Username)
{
MSALCache appTokenCache = new MSALCache(clientId);
ClientCredential clientdummy = new ClientCredential(clientSecret);
ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(clientId, string.Format(AuthorityFormat, tenantId), redirectUri,
clientdummy, null, null);
authenticate(daemonClient).Wait();
string token = authResult.AccessToken;
client = GetAuthenticatedClientForApp(token);
IGraphServiceUsersCollectionPage users = client.Users.Request().GetAsync().Result;
}
private async Task<AuthenticationResult> authenticate(ConfidentialClientApplication daemonClient)
{
authResult = await daemonClient.AcquireTokenForClientAsync(new[] { MSGraphScope });
return authResult;
}
Found Workaround Solution: getting a Token via REST API. Here I can get an User token or an Client token to access to graph api:
var client = new RestClient("https://login.microsoftonline.com/" + domainname);
var request = new RestRequest("/oauth2/token", Method.POST); request.AddBody("grant_type", "client_credentials");
request.AddParameter("client_id", clientId);
request.AddParameter("client_secret", clientSecret);
request.AddParameter("Resource", "https://graph.microsoft.com");
request.AddParameter("scope", "[scopes]");
IRestResponse response = client.Execute(request);
//contains the token
var content = response.Content;
According to your description, I assume you need a solution to authenticate a user without any interaction.
We can get an Access Token by some background services or daemons.
For more details, we can refer to this document.
Base on my test, we can try the following steps:
First, we should get administrator consent:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Scope = "openid profile",
ResponseType = "id_token",
TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, NameClaimType = "name" },
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = this.OnAuthenticationFailedAsync,
SecurityTokenValidated = this.OnSecurityTokenValidatedAsync
}
});
ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(Startup.clientId, string.Format(AuthorityFormat, tenantId), Startup.redirectUri,
new ClientCredential(Startup.clientSecret), null, appTokenCache.GetMsalCacheInstance());
AuthenticationResult authResult = await daemonClient.AcquireTokenForClientAsync(new[] { MSGraphScope });
Then, we can use this access token to using the Graph APIs.
For more details, we can review to v2.0 daemon sample on GitHub.
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
To authenticate with Azure in azure sdk fluent nuget, there is a method that uses client id and secret as below
var azureCredentials = new AzureCredentials(new
ServicePrincipalLoginInformation
{
ClientId = "ClientId",
ClientSecret = "ClientSecret"
}, "tenantId", AzureEnvironment.AzureGlobalCloud);
Is there any interface where authentication token (JWT) can be used instead of using client id and secret while creating IAzure in the below code?
_azure = Azure
.Configure()
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.Authenticate(azureCredentials)
.WithSubscription(_subscriptionId);
Note: I have a separate authenticater module that keeps client id and secret with itself and uses them to get authentication token which will be used by other components/sdks.
The answer is actually yes, you can use the authentication token (JWT). It's just not that obvious.
var context = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId, false);
var result = context.AcquireToken("https://management.core.windows.net/", clientId, new Uri("http://localhost"), PromptBehavior.Always);
var token = result.AccessToken;
var client = RestClient
.Configure()
.WithEnvironment(AzureEnvironment.AzureGlobalCloud)
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.WithCredentials(new TokenCredentials(token))
.Build();
var azure = Azure
.Authenticate(client, tenantId)
.WithSubscription(subscriptionId);
Sigh...they've changed the WithCredentials to use an AzureCredentials instead of a ServiceClientCredentials. Here's an updated version:-
var context = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId, false);
var result = context.AcquireToken("https://management.core.windows.net/", clientId, new Uri("http://localhost"), PromptBehavior.Always);
var token = result.AccessToken;
var tokenCredentials = new TokenCredentials(token);
var azureCredentials = new AzureCredentials(
tokenCredentials,
tokenCredentials,
tenantId,
AzureEnvironment.AzureGlobalCloud);
var client = RestClient
.Configure()
.WithEnvironment(AzureEnvironment.AzureGlobalCloud)
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.WithCredentials(azureCredentials)
.Build();
var azure = Azure
.Authenticate(client, tenantId)
.WithSubscription(subscriptionId);
Starting from Azure Management Fluent SDK v1.10 you can use any credentials provider that is derived from ServiceClientCredentials. In other words you should be able to pass already acquired Bearer token string to AzureCredentials constructor like this
var customTokenProvider = new AzureCredentials(
new TokenCredentials(armAuthToken),
new TokenCredentials(graphAuthToken),
tenantId,
AzureEnvironment.AzureGlobalCloud);
var client = RestClient
.Configure()
.WithEnvironment(AzureEnvironment.AzureGlobalCloud)
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.WithCredentials(customTokenProvider)
.Build();
var authenticatedClient = Azure.Authenticate(client, tenantId);
Is there any interface where authentication token (JWT) can be used instead of using client id and secret while creating IAzure in the below code?
In short no. Base on my experence, if we want to access the corresponding Azure resources then we need to get the authentication token (JWT) from the corresponding resources. Azure has lots of resources such as AzureSQL, KeyVault, ResourceManagement etc.
On my option, it is not make senses that use the authentication token (JWT) that can access all of the Azure Resources.
Currently, we could get AzureCredentials from file, ServicePrincipalLoginInformation, UserLoginInformation
If we want to operate a certain resource, then we could use authentication token (JWT) to do, take KeyVault as example.
public static async Task<string> GetAccessToken(string azureTenantId,string azureAppId,string azureSecretKey)
{
var context = new AuthenticationContext("https://login.windows.net/" + azureTenantId);
ClientCredential clientCredential = new ClientCredential(azureAppId, azureSecretKey);
var tokenResponse =await context.AcquireTokenAsync("https://vault.azure.net", clientCredential); //KeyVault resource : https://vault.azure.net
var accessToken = tokenResponse.AccessToken;
return accessToken;
}
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessToken));
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