acquiretokenasync not working in a web app - azure

I am trying to use the following overload :
authContext.AcquireTokenAsync(, )
This works fine with a simple console app and I am able to retrieve the token.
But when I run this from a web application, the call doesn't return and no exception is thrown. I checked in fiddler and it seems the connection closes on this call.
How to get this resolved? Is it related to HttpContext having restricted permissions?

Its probably because of the async/await, for my code I just added await before the graphClient.Users[FromUserEmail].SendMail(message, true).
This works:
var sendmail = graphClient.Users[FromUserEmail].SendMail(message, true);
Task.Run(async () =>
{
try
{
await sendmail.Request().PostAsync();
}
catch (Exception ex)
{
throw ex;
}
}).Wait();

Normally, we acquire the token using authorization code grant flow in a web application. To achieve the goal, we need to implement OnAuthorizationCodeReceived event like below(full code sample):
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
// If you create the redirectUri this way, it will contain a trailing slash.
// Make sure you've registered the same exact Uri in the Azure Portal (including the slash).
Uri uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, graphResourceId);
}
If you want to implement the client credentials flow, you can refer code below:
string authority = "https://login.microsoftonline.com/{tenantId}";
string clientId = "{clientId}";
string secret = "{secret}";
string resource = "https://graph.windows.net";
var credential = new ClientCredential(clientId, secret);
AuthenticationContext authContext = new AuthenticationContext(authority);
var token = authContext.AcquireTokenAsync(resource, credential).Result.AccessToken;
If you still have the problem, it is helpful to share the detail code.

it's just a common mistakes that developper makes regarding asyn/await
you have just to add async Task<> to your method and of course the await keyword near to the call of methode that retreive the token

Related

How to create KeyVaultClient instance using existing access_token string?

I have implemented Key Vault access token generator using below codebase:
private async Task<string> GetStaticToken(string authority, string resource)
{
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(_appSettings.ClientId, _appSettings.ClientSecret);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, credential);
return result.AccessToken;
}
I know how to use this token into Authorization header and get the secret values using Rest API call. But can we use the same AccessToken string into below code base:
var builder = new ConfigurationBuilder();
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
builder.AddAzureKeyVault($"https://{myVaultName}.vault.azure.net/", keyVaultClient, new DefaultKeyVaultSecretManager());
Configuration = builder.Build();
Here is it possible to re-use AccessToken string value, while creating KeyVaultClient? Something like below:
var tokenValue = GetStaticToken (authority, resource);
var keyVaultClient = new KeyVaultClient(tokenValue);
Basically I would like to generate token at once and reuse string everywhere, even outside my application scope.
Note: I am aware that token will come with expiration time duration. That time GetToken will be called again.
Well, you can make a callback that returns that token:
var kvClient = new KeyVaultClient((authority, resource, scope) => Task.FromResult(tokenValue));
This simply replaces the call to get a token with an already completed Task with the token in it.

Unable to get AccessToken with Azure B2C, IdToken present but no scope permissions

I'm following this tutorial to setup a client / server using Azure B2C.
I think I've done everything correctly but I'm experiencing a couple of issues.
AccessToken is null, but IdToken is populated
When I try to access protected resources, the following code is executed after I sign in at https://login.microsoftonline.com. The following line fails because AccessToken is null:
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
TaskWebApp.Controllers.TaskController.cs (Client App):
public async Task<ActionResult> Index()
{
try
{
// Retrieve the token with the specified scopes
var scope = new string[] { Startup.ReadTasksScope };
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, this.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(Startup.ClientId, Startup.Authority, Startup.RedirectUri, new ClientCredential(Startup.ClientSecret), userTokenCache, null);
var user = cca.Users.FirstOrDefault();
if (user == null)
{
throw new Exception("The User is NULL. Please clear your cookies and try again. Specifically delete cookies for 'login.microsoftonline.com'. See this GitHub issue for more details: https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi/issues/9");
}
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scope, user, Startup.Authority, false);
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiEndpoint);
// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken); //AccessToken null - crash
//request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.IdToken); //This does work however
}
...
}
Contents of result AquireTokenSilentAsync:
IdToken doesn't contain Scope permissions
If I use IdToken in place of AccessToken - I get a little further but I'm hitting a new stumbling block. It fails here:
TaskService.Controllers.TasksController.cs (WebAPI):
public const string scopeElement = "http://schemas.microsoft.com/identity/claims/scope";
private void HasRequiredScopes(String permission)
{
if (!ClaimsPrincipal.Current.FindFirst(scopeElement).Value.Contains(permission)) //Crashes here as token doesn't contain scopeElement
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.Unauthorized,
ReasonPhrase = $"The Scope claim does not contain the {permission} permission."
});
}
}
And here is a screenshot of my ClaimsPrincipal.Current:
Any advice is appreciated.
Edit
Signin URL:
https://login.microsoftonline.com/te/turtlecorptesting.onmicrosoft.com/b2c_1_email/oauth2/v2.0/authorize?client_id=03ef2bd...&redirect_uri=https%3a%2f%2flocalhost%3a44316%2f&response_mode=form_post&response_type=code+id_token&scope=openid+profile+offline_access+https%3a%2f%2fturtlecorptesting.onmicrosoft.com%2fread+https%3a%2f%2fturtlecorptesting.onmicrosoft.com%2fwrite&state=OpenIdConnect.AuthenticationProperties%3daDQntAuD0Vh=...&nonce=63655.....YWRmMWEwZDc.....
Under Azure AD B2C go to your application
Under API access click add, select your API and its scope(s)
You should now get AccessToken in your response.

How to get user's basic details from MS Graph API

I am trying to get full name of a user using MS Graph API. The code is not working with delegated permission User.ReadBasic.All while working with App permission of User.Read.All
code is:
public static async Task<string> GetAccessToken()
{
string authorityUri = $"https://login.microsoftonline.com/{tenantid}";
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
string resourceUrl = "https://graph.microsoft.com";
ClientCredential creds = new ClientCredential(ConfigHelper.ClientId, ConfigHelper.AppKey);
AuthenticationResult authResult = await authContext.AcquireTokenAsync(resourceUrl, creds);
return authResult.AccessToken;
}
public static async Task<GraphServiceClient> GetGraphClient()
{
GraphServiceClient graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await GetAccessToken();
if (!string.IsNullOrEmpty(accessToken))
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
}
}));
return graphServiceClient;
}
Error is
Microsoft.Graph.ServiceException: Code: Authorization_RequestDenied
Message: Insufficient privileges to complete the operation. Blockquote
I am not sure why this is happening.
Edited:
private static async Task<string> GetAccessToken()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
string resourceUrl = "https://graph.microsoft.com";
//// get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
ClientCredential clientcred = new ClientCredential(ConfigHelper.ClientId, ConfigHelper.AppKey);
//// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
AuthenticationContext authenticationContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenantid}") ;
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(resourceUrl, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
return authenticationResult.AccessToken;
}
Well, you are using Client Credentials Grant Flow, expecting delegated permissions to be applied to a scenario where there is no user.
The token you get is for the app acting purely as itself, which means only Application Permissions will be applied. Delegated permissions are only applied when there is a user context.
Your options:
Use Application permissions
Use a flow like Authorization Code Grant Flow to get a delegated access token
You get a refresh token too when you get a token in this way, which you can use to get a new access token for this user whenever you want (+ a new refresh token)
Though certain situations like the user resetting their password will cause the refresh token to be revoked

OpenIdConnectResponseTypes has codeidtoken ,idtoken and it doesnt contain code as response type

OpenIdConnectResponseTypes has codeidtoken ,idtoken and it doesnt contain code as response type. Does UseOpenIdConnectAuthentication in OWIN support Authorization Code grant? By default it sets the responsetype as Code IDToken. Can someone share the sample for Authorization code grant using OWIN ?
From source code of Katana (below code could be found in OpenIDConnectAuthenticationHandler.AuthenticateCoreAsync method):
// code is only accepted with id_token, in this version, hence check for code is inside this if
// OpenIdConnect protocol allows a Code to be received without the id_token
if (string.IsNullOrWhiteSpace(openIdConnectMessage.IdToken))
{
_logger.WriteWarning("The id_token is missing.");
return null;
}
Above code shows Microsoft.Owin.Security.OpenIdConnect library doesn't support Authorization Code grant . Though not directly supported, you can also use the hybrid flow , but it's up to you to implement the token request part , please refer to below code which use code to exchange the access token for resource protected by azure ad :
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
//
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
//
AuthorizationCodeReceived = async (context) =>
{
var code = context.Code;
// Create a Client Credential Using an Application Key
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(
"http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
}
}
}

ADAL authentication without dialog box prompt

I have a console application registered in Azure AD that connects to CRM Online (configured using these steps). It queries the Web API.
The application needs to run with no user interaction... but unfortunately the call to AcquireTokenSilentAsync always fails and only AcquireTokenAsync works. This makes a user login dialog appear which fails the user interaction requirement!
Is there any way to prevent this prompt, either by saving the login somewhere on the client machine (which hasn't worked so far) or perhaps using a certificate (but how do you do this?) or something else?
I'm using the ADAL for .NET v3.10.305110106 release. The following code is used to authenticate:
private static async Task PerformOnlineAuthentication()
{
_authInfo = new AuthInfo(); // This is just a simple class of parameters
Console.Write("URL (include /api/data/v8.x): ");
var url = Console.ReadLine();
BaseUri = new Uri(url);
var absoluteUri = BaseUri.AbsoluteUri;
_authInfo.Resource = absoluteUri;
Console.Write("ClientId: ");
var clientId = Console.ReadLine();
_authInfo.ClientId = clientId;
Console.Write("RedirectUri: ");
var redirectUri = Console.ReadLine();
_authInfo.RedirectUri = new Uri(redirectUri);
var authResourceUrl = new Uri($"{_authInfo.Resource}/api/data/");
var authenticationParameters = await AuthenticationParameters.CreateFromResourceUrlAsync(authResourceUrl);
_authInfo.AuthorityUrl = authenticationParameters.Authority;
_authInfo.Resource = authenticationParameters.Resource;
_authInfo.Context = new AuthenticationContext(_authInfo.AuthorityUrl, false);
}
private static async Task RefreshAccessToken()
{
if (!IsCrmOnline())
return;
Console.WriteLine($"Acquiring token from: {_authInfo.Resource}");
AuthenticationResult authResult;
try
{
authResult = await _authInfo.Context.AcquireTokenSilentAsync(_authInfo.Resource, _authInfo.ClientId);
}
catch (AdalSilentTokenAcquisitionException astae)
{
Console.WriteLine(astae.Message);
authResult = await _authInfo.Context.AcquireTokenAsync(_authInfo.Resource, _authInfo.ClientId, _authInfo.RedirectUri, new PlatformParameters(PromptBehavior.RefreshSession));
}
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}
Thanks to #aravind who pointed out the active-directory-dotnet-native-headless sample.
The sample contains a FileCache class which inherits from Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache. That class manages caching of the credentials to an encrypted file on disk. This means that there is only one prompt on first run and after that the credentials are locally stored.
The final pieces of the puzzle are:
Calling a different constructor signature to initialize AuthenticationContext with the FileCache:
_authInfo.Context = new AuthenticationContext(
_authInfo.AuthorityUrl, false, new FileCache());
Obtaining credentials from the user into a Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential object (see the TextualPrompt() method in the sample)
Passing the credentials to a different method signature for AcquireTokenAsync():
authResult = await _authInfo.Context.AcquireTokenAsync(
_authInfo.Resource, _authInfo.ClientId, userCredential);
If "application needs to run with no user interaction" use ClientCredential flow eg:
public static string GetAccessTokenUsingClientCredentialFlow(Credential cred) {
AuthenticationContext ac = new AuthenticationContext(cred.Authority);
AuthenticationResult r = ac.AcquireTokenAsync(cred.ResourceId, new ClientCredential(cred.ClientId, cred.ClientSecret)).Result;
return r.AccessToken;
}

Resources