I have created a windows app which is calling some app hosted in azure. App service is using AAD for authentication.
Following is the method I am using for MS login and storing token.
authContext = new AuthenticationContext(authority, new FileCache());
authContext.AcquireTokenAsync(todoListResourceId, clientId, redirectUri, new PlatformParameters(PromptBehavior.Always)).ContinueWith(t =>
{
result = t.Result;
})
.Wait();
By using this above method, I am successfully able to login using MS credential and getting access token from result.
Now I am passing this token in header request to get some data from app which has SSO enabled like :-
using (var client = new HttpClient())
{
var uri = "http://appservice.azurewebsites.net/api/values/5";
_auth.GetToken().ContinueWith(t => { token = t.Result; }).Wait();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var TaskAPI = client.GetAsync(uri).ContinueWith(task =>
{
if (task.Status == TaskStatus.RanToCompletion)
{
var response = task.Result;
if (response.IsSuccessStatusCode)
{
flag = 1;
var data = response.Content.ReadAsStringAsync().Result;
}
}
});
TaskAPI .Wait();
}
Get Token function is acquiring token silently
Below is GetToken() used to fetch token for calling API
authContext.AcquireTokenSilentAsync(todoListResourceId, clientId)
.ContinueWith(i =>
{
result = i.Result;
}).Wait();
return result.AccessToken;
When I call this URI by passing retrieved token, I get response of Un Authorized(401).
How can I check if token is proper or I am missing something or if there is any other way to do that?
Thanks
Subham,Nathcorp
Related
I have tried other answers before I post this question.
I have created a web API and published into azure web app. I have enabled azure AD authentication. I have created one app registration and given permissions to AD app. In my console application, I am calling as below:
private static async Task<string> GetToken()
{
string aadInstance = "https://login.windows.net/common";
string ResourceId = ConfigurationManager.AppSettings["ResourceId"];
string tenantId = ConfigurationManager.AppSettings["TenantId"];
string clientId = ConfigurationManager.AppSettings["ClientId"];
string replyAddress = ConfigurationManager.AppSettings["ReplyAddressConfigured"];
string password = ConfigurationManager.AppSettings["ida:Password"];
AuthenticationContext authenticationContext =
new AuthenticationContext(string.Format(aadInstance, tenantId));
PlatformParameters k = new PlatformParameters(PromptBehavior.Auto);
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(ResourceId, clientId, new Uri(replyAddress),k).ConfigureAwait(false);
return authenticationResult.AccessToken;
}
static async Task Main(string[] args)
{
var accessToken = await GetToken();
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var responseTask = client.GetAsync("https://myapi.azurewebsites.net/api/GetData/GetLicensing?appName=test&tenantId=f556301-8a1e-48c3-b59e-55a9036f843e");
responseTask.Wait();
var result = responseTask.Result;
if (result.IsSuccessStatusCode)
{
var readTask = result.Content.ReadAsStringAsync();
readTask.Wait();
}
}
}
IsSuccessStatusCode code is coming as false with 401. What is the mistake here. But if I use client and secret it is successful. It is not possible to call without passing client secret value?
Below is just illustration:
You cannot get correct token without client secret if you are calling the API without using MSI.
But, if you use MSI, in both API and hosted Console app on cloud, you can call API from the Console app without Client secret.
I am trying to create Online Meeting using microsoft graph api without login into AzureActiveDirectory with asp.net web application.For this my app has below permissions which are required as per documentation https://learn.microsoft.com/en-us/graph/api/application-post-onlinemeetings?view=graph-rest-1.0&tabs=csharp with client credential auth flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow without immediate interaction with a user.I am able to retrive access token successfully as per client-creds-grant-flow.
I tried Micosoft.Graph and Micosoft.Graph.Beta still getting 404 error.
Create online meeting code
var graphClient = GetAuthenticatedClientCredential();
var onlineMeeting = new OnlineMeeting
{
StartDateTime = DateTimeOffset.Parse("2020-10-01T10:30:34.2444915+00:00"),
EndDateTime = DateTimeOffset.Parse("2020-10-01T11:00:34.2464912+00:00"),
Subject = "Create Online Meeting-Without user login to Office 365"
};
return await graphClient.Me.OnlineMeetings
.Request()
.AddAsync(onlineMeeting);
Access Token code
public static async Task<string> GetUserAccessTokenAsyncByCc()
{
IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder.Create(appId)
.WithTenantId(appTenantId)
.WithClientSecret(appSecret)
.Build();
string[] scopes1 = new string[] { "https://graph.microsoft.com/.default" };
//string[] scopes1 = new string[] { "https://graph.microsoft.com/OnlineMeetings.ReadWrite.All" };
// string[] scopes1 = new string[] { "https://graph.microsoft.com/beta/OnlineMeetings.Read.All" };
//string[] scopes1 = new string[] { "https://graph.microsoft.com/beta/.default" };
var result = await cca.AcquireTokenForClient(scopes1).ExecuteAsync();
return result.AccessToken;
}
and Auth Provider code
public static GraphServiceClient GetAuthenticatedClientCredential()
{
DelegateAuthenticationProvider provider = new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await GetUserAccessTokenAsyncByCc();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
});
GraphServiceClient graphClient = new GraphServiceClient(provider);
return graphClient;
}
app permission image
below are the necessary app permission
You can only use delegated permissions to create an onlineMeeting, so you must log in as a user, and you cannot use the client credential flow. You need to use the auth code flow to obtain the token.
I'm currently in the process of converting my Power BI embedded application from .NET to .NET core.
My old code for generating tokens looked something like this:
var credential = new UserPasswordCredential(Username, Password);
var authContext = new AuthenticationContext(AuthorityUrl);
var authResult = authContext.AcquireTokenSilentAsync(ResourceUrl, _applicationId).Result;
However, by design .NET Core does not support UserPasswordCredential.
Gunnar Peipman in a recent article "Embedded Power BI reports with ASP.NET Core" used a HTTP request to solve this, but is this the recommended approach?
private async Task<string> GetPowerBIAccessToken(PowerBISettings powerBISettings)
{
using(var client = new HttpClient())
{
var form = new Dictionary<string, string>();
form["grant_type"] = "password";
form["resource"] = powerBISettings.ResourceUrl;
form["username"] = powerBISettings.UserName;
form["password"] = powerBISettings.Password;
form["client_id"] = powerBISettings.ApplicationId.ToString();
form["client_secret"] = powerBISettings.ApplicationSecret;
form["scope"] = "openid";
client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/x-www-form-urlencoded");
using (var formContent = new FormUrlEncodedContent(form))
using (var response = await client.PostAsync(powerBISettings.AuthorityUrl, formContent))
{
var body = await response.Content.ReadAsStringAsync();
var jsonBody = JObject.Parse(body);
var errorToken = jsonBody.SelectToken("error");
if(errorToken != null)
{
throw new Exception(errorToken.Value<string>());
}
return jsonBody.SelectToken("access_token").Value<string>();
}
}
}
No, the recommended approach is to use ClientCredential class.
The idea here is that for apps to collect and store a user's username and password is not the correct approach for non-interactive authentication.
Also see Microsoft identity platform and the OAuth 2.0 client credentials flow and Acquiring Tokens:
Confidential client applications the flows will rather:
Acquire token for the application itself, not for a user, using client credentials.
And one more quote:
Pattern to acquire tokens in MSAL 3.x
All the Acquire Token methods in MSAL 3.x have the following pattern:
from the application, you call the AcquireTokenXXX method corresponding to the flow you want to use, passing the mandatory parameters for this flow (in general flow)
this returns a command builder, on which you can add optional parameters using .WithYYY methods
then you call ExecuteAsync() to get your authentication result.
Here is the pattern:
AuthenticationResult result = app.AcquireTokenXXX(mandatory-parameters)
.WithYYYParameter(optional-parameter)
.ExecuteAsync();
An example (again by Gunnar) how to do this:
public async Task<AuthenticationResult> RequestTokenAsync(
ClaimsPrincipal claimsPrincipal,
string authorizationCode,
string redirectUri,
string resource)
{
try
{
var userId = claimsPrincipal.GetObjectIdentifierValue();
var issuerValue = claimsPrincipal.GetIssuerValue();
var authenticationContext = await CreateAuthenticationContext(claimsPrincipal)
.ConfigureAwait(false);
var authenticationResult = await authenticationContext.AcquireTokenByAuthorizationCodeAsync(
authorizationCode,
new Uri(redirectUri),
new ClientCredential(_adOptions.ClientId, _adOptions.ClientSecret),
resource)
.ConfigureAwait(false);
return authenticationResult;
}
catch (Exception)
{
throw;
}
}
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
var outlookServicesClient = await AuthenticationHelper.EnsureOutlookServicesClientCreatedAsync("Calendar");
internal static async Task<OutlookServicesClient> EnsureOutlookServicesClientCreatedAsync(string capabilityName)
{
var signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
try
{
DiscoveryClient discClient = new DiscoveryClient(SettingsHelper.DiscoveryServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(SettingsHelper.DiscoveryServiceResourceId, new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret),
new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
var dcr = await discClient.DiscoverCapabilityAsync(capabilityName);
return new OutlookServicesClient(dcr.ServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(dcr.ServiceResourceId,
new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret),
new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
}
catch (AdalException exception)
{
//Handle token acquisition failure
if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
authContext.TokenCache.Clear();
throw exception;
}
return null;
}
public ADALTokenCache(string user)
{
// associate the cache to the current user of the web app
User = user;
this.AfterAccess = AfterAccessNotification;
this.BeforeAccess = BeforeAccessNotification;
this.BeforeWrite = BeforeWriteNotification;
// look up the entry in the DB
Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == User);
// place the entry in memory
this.Deserialize((Cache == null) ? null : Cache.cacheBits);
}
i am using this code for ADAL authentication. This is working fine in my local IIS server. When i hosted the same on AZURE VM then getting an error like
"Failed to acquire token silently. Call method AcquireToken". Can anybody help me on resolving this error??
Settings Helper code as follows. In public ADALTokenCache(string user) we are getting userid finely but getting an empty cache... What will be the reason??
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
try
{
DiscoveryClient discClient = new DiscoveryClient(SettingsHelper.DiscoveryServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(SettingsHelper.DiscoveryServiceResourceId,
new ClientCredential(SettingsHelper.ClientId,
SettingsHelper.ClientSecret),
new UserIdentifier(userObjectId,
UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
Make sure that your authority does not contain "common". Also, please turn on the diagnostics as explained in http://www.cloudidentity.com/blog/2015/08/07/adal-diagnostics/ and take a look at the trace. Very often this is due to a mismatch in the cache - acquiretokensilent only works with cached tokens, and if you didn't seed the cache/you are not working against the cache instance you selected earlier/you pass a different user identifier/you pass common as authority you'll get a cache miss.
I assume you were using the O365-ASPNETMVC-Start project on Github.
What's the "ida:TenantId" setting in your web.config file on Azure VM?
I can get the same error "Failed to acquire token silently. Call method AcquireToken" if setting the "ida:TenantId" to "common". For this scenerio, you need to set "ida:TenantId" to actual tenant id. For example, "e07xxxx0e-fxx2-441f-ad9a-9dxxa59xxx52" (guid).