I have implemented Azure AD auth following the below sample:
https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect
Here is the code from my application. The users are getting intermittent exception "Failed to acquire token silently. Call method token acquisition". Any help would be highly appreciated.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = Authority,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = (context) =>
{
string userObjectId = null;
var code = context.Code;
var currentClaimsIdentity = context.AuthenticationTicket.Identity;
if (currentClaimsIdentity != null)
{
userObjectId = currentClaimsIdentity.FindFirst(Constants.ObjectIdentifierClaimType).Value;
}
ClientCredential credential = new ClientCredential(ClientId, AppKey);
AuthenticationContext authContext = new AuthenticationContext(Authority, new SessionCache(userObjectId, HttpContext.Current));
authContext.AcquireTokenByAuthorizationCode(code, StandardSettings.ReplyUrl, credential, Constants.GraphResourceBaseUrl);
return Task.FromResult(0);
},
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/");
return Task.FromResult(0);
}
}
});
/// <summary>
/// Gets the access token.
/// </summary>
/// <returns>The access token for service call.</returns>
private string GetAccessToken()
{
string userName = null;
AuthenticationResult authenticationResult = null;
ClaimsPrincipal currentClaimsPrincipal = ClaimsPrincipal.Current;
if (currentClaimsPrincipal != null)
{
userName = currentClaimsPrincipal.FindFirst(ClaimTypes.Name).Value;
}
try
{
authenticationResult = this.GetAuthenticationResult();
if (authenticationResult.ExpiresOn < DateTimeOffset.UtcNow)
{
Trace.TraceWarning("Access token expired for the user: {0}. Challenge the user authentication to get a new token.", userName);
this.httpCurrentContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
catch (AdalSilentTokenAcquisitionException ex)
{
Trace.TraceWarning("Failed to acquire the token for the user: {0} with exception: {1}. Challenge the user authentication for retry.", userName, ex);
this.httpCurrentContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
if (authenticationResult == null)
{
try
{
authenticationResult = this.GetAuthenticationResult();
}
catch (Exception ex)
{
Trace.TraceWarning("Failed to acquire the token on the retry for the user: {0} with the exception: {1}.", userName, ex);
throw new AdalException(
AdalError.FailedToAcquireTokenSilently,
"The session expired or the token cache was reset. Please sign out and then navigate to the url again to re-authenticate.");
}
}
return authenticationResult.AccessToken;
}
/// <summary>
/// Get the authentication result for the request.
/// </summary>
/// <returns>The authentication result.</returns>
private AuthenticationResult GetAuthenticationResult()
{
string userObjectId = null;
ClaimsPrincipal currentClaimsPrincipal = ClaimsPrincipal.Current;
if (currentClaimsPrincipal != null)
{
userObjectId = currentClaimsPrincipal.FindFirst(Constants.ObjectIdentifierClaimType).Value;
}
AuthenticationContext authContext = new AuthenticationContext(
Startup.Authority,
new SessionCache(userObjectId, this.httpCurrentContext));
ClientCredential credential = new ClientCredential(Startup.ClientId, Startup.AppKey);
return authContext.AcquireTokenSilent(
Constants.GraphResourceBaseUrl,
credential,
new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
}
That message appears for a variety of reasons:
the cache you are using is empty
the cache does not contain a valid refresh token (expired, etc)
the cache does not contain a refresh token for the authority/clientid/user combination you specified
the identifier for the user does not correspond to the actual user identifier that was originally issued in the token
Related
I am developing a game using Monogame and I want to access my Api so that I can make a login but it always returns me an exception. If I use port 80 I get the following one No connection could be made because the target machine actively refused it and if I use port 5000 I get a 401: Not authorized .
By printing in the console I could come to the conclusion that my try is interrupted at the line response = await client.GetStringAsync(builder.Uri.AbsoluteUri);
Is there something wrong with my code?
Communication class
public class Communication
{
private readonly HttpClient client = new HttpClient();
private const string Uri = "http://localhost:5000/";
private const int Port = 5000;
public Communication()
{
}
public async Task<User> Login(string username, string password)
{
string response;
User user = null;
try
{
var builder = new UriBuilder(Uri + "/Api/Account/Login/")
{
Port = Port
};
builder.Query = $"Username={username}&Password={password}";
Debug.WriteLine("Chegou Aqui!!!!")
response = await client.GetStringAsync(builder.Uri.AbsoluteUri);
if (response == "OK")
{
user = JsonConvert.DeserializeObject<User>(response);
}
}
catch (Exception ex)
{
Debug.WriteLine("\tERROR {0}", ex.Message);
}
return user;
}
}
My Api Login Method
[AllowAnonymous]
[HttpPost("Login")]
public IActionResult Authenticate([FromBody]AuthenticateModel userModel)
{
var user = _userService.Authenticate(userModel.Username, userModel.Password);
if(user == null)
{
return BadRequest(new { message = "Username or Password invalid" });
}
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
Id = user.Id,
UserName = user.Username,
Token = tokenString
}) ;
}
This article possibly can help you
You can check your firewall settings or if any antivirus installed in your system, make sure to check the settings of it.
You get error 401: Not authorized because HTTP requests by default configured on port 80, not any other ports.
If the problem persists, check your router or switch configuration, if any is in your route to server.
For an integration test I have an authorized .NET Core 2.2 Controller that is calling another authorized controller (different project) or external api (like Microsoft Graph).
Both apis are authenticated against the Azure AD. In all the controller actions we need the authenticated user.
We can get in the first api by getting a token based on the username and password (grant_type=password). When the call continues to the second api, it breaks because of an interactive login prompt (We use ADAL).
Normally, the user authenticates with open id connect, we then have the authentication code and get the accesstoken + refresh token with the authentication code. With the refresh token we can get an access token for the second api.
We created a small sample project with default Values Controllers to explain our problem.
Get access token before calling the first api with native app registration:
public static async Task<string> AcquireTokenAsync(string username, string password)
{
var aadInstance = "https://login.windows.net/{0}";
var tenantId = "put id here";
var authority = string.Format(aadInstance, tenantId);
var clientId = "clientid here";
var resource = "put resource here";
var client = new HttpClient();
var tokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/token";
var body = $"resource={resource}&client_id={clientId}&grant_type=password&username={username}&password={password}";
var stringContent = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");
var result = await client.PostAsync(tokenEndpoint, stringContent).ContinueWith((response) =>
{
return response.Result.Content.ReadAsStringAsync().Result;
});
JObject jobject = JObject.Parse(result);
var token = jobject["access_token"].Value<string>();
return token;
}
First API:
[Authorize]
[HttpGet]
public async Task<IActionResult> Get()
{
string name = User.Identity.Name;
var result = await AcquireTokenSilentWithImpersonationAsync();
string BaseUrl = "https://localhost:44356/";
var client = new HttpClient
{
BaseAddress = new Uri(BaseUrl)
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
var url = "api/values";
HttpResponseMessage response = await client.GetAsync(url);
switch (response.StatusCode)
{
case HttpStatusCode.OK:
int x = 1;
break;
default:
throw new HttpRequestException($"Error - {response.StatusCode} in response with message '{response.RequestMessage}'");
}
return Ok();
}
private const string BackendResource = "Second api resource here";
/// <summary>
/// For more information: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-devhowto-adal-error-handling
/// </summary>
/// <returns></returns>
public async Task<AuthenticationResult> AcquireTokenSilentWithImpersonationAsync()
{
const string ClientId = "client id of first api here";
const string ClientSecret = "secret of first api here";
ClientCredential credential = new ClientCredential(ClientId, ClientSecret);
string userObjectId = _httpContextAccessor.HttpContext.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
var authContext = GetAuthenticationContext(userObjectId);
AuthenticationResult authResult = null;
try
{
authResult = await authContext.AcquireTokenSilentAsync(BackendResource, credential, new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
}
catch (AdalSilentTokenAcquisitionException ex)
{
// Exception: AdalSilentTokenAcquisitionException
// Caused when there are no tokens in the cache or a required refresh failed.
// Action: Case 1, resolvable with an interactive request.
try
{
authResult = await authContext.AcquireTokenAsync(BackendResource, ClientId, new Uri("https://backurl.org"), new PlatformParameters(), new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
}
catch (Exception exs)
{
throw;
}
}
catch (AdalException e)
{
// Exception: AdalException
// Represents a library exception generated by ADAL .NET.
// e.ErrorCode contains the error code.
// Action: Case 2, not resolvable with an interactive request.
// Attempt retry after a timed interval or user action.
// Example Error: network_not_available, default case.
throw;
}
return authResult;
}
Second api:
[Authorize]
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
string name = User.Identity.Name;
return new string[] { "value1", "value2" };
}
You need to use the On-behalf-of flow in your Web API (not the interactive token acquisition, need)
If you want to use ADAL.NET, a sample is there: https://github.com/azure-samples/active-directory-dotnet-webapi-onbehalfof
but I would now recommend you use MSAL.NET. the sample is: active-directory-dotnet-native-aspnetcore-v2/2. Web API now calls Microsoft Graph, and the documentation: https://aka.ms/msal-net-on-behalf-of
Also note that for Web APIs, we don't use OIDC (this is to sign-in users), but rather a JWT bearer middleware
I wanted to send a mail using SMTP server with Azure access token.
I have an application hosted in azure and user login with tenant active directory. I found a code which does the same for google account here.
Can someone help me?
I tried with the below code, generated azure access token and tried to send it to SMTP as below. But something is missing here and going in other way as this code is not sending mail.
public async Task SendmailusingOAuthToken()
{
try
{
string accessToken = await new AppConfig().GetTokenForApplication();
System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage(
"Anita.Sure#mindtree.com",
"Anita.Sure#mindtree.com",
"NETWORKNET-33499 - " + Guid.NewGuid().ToString(),
"Access to SMTP servers using OAuth");
using (SmtpClient client = new SmtpClient("smtp.office365.com", 587))//, "user1#testaccount1913.narod2.ru", accessToken, true))
{
client.Timeout = 400000;
//client.SecurityMode = SmtpSslSecurityMode.Implicit;
client.EnableSsl = true;
client.SendAsync(message, accessToken);
}
}
catch (Exception ex)
{
throw;
}
}
Token generation code:
public async Task<string> GetTokenForApplication()
{
try
{
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;
// get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
ClientCredential clientcred = new ClientCredential(clientId, appKey);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's token filecache
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new FileCache(signedInUserID));
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(graphResourceID, clientcred,
new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
return authenticationResult.AccessToken;
}
catch (Exception ex)
{
throw;
}
}
We want to use AcquireTokenAsync to acquire the tokens on behalf of user using the following syntax
public static async Task<UserTokenCache> GetAccessTokens(string userUniqueId)
{
UserTokenCache cache = null;
AuthenticationContext authContext = null;
ClientCredential credential = new ClientCredential(clientId, appKey);
AuthenticationResult powerBIResult = null;
AuthenticationResult graphResult = null;
bool isAdalException = false;
try
{
authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userUniqueId));
powerBIResult = await authContext.AcquireTokenSilentAsync(pbiResourceID, credential, new UserIdentifier(userUniqueId, UserIdentifierType.UniqueId));
graphResult = await authContext.AcquireTokenSilentAsync(graphResourceId, credential, new UserIdentifier(userUniqueId, UserIdentifierType.UniqueId));
cache = new UserTokenCache
{
GraphAccessToken = graphResult.AccessToken,
PBIAccessToken = powerBIResult.AccessToken,
PBITokenExpires = powerBIResult.ExpiresOn,
GraphTokenExpires = graphResult.ExpiresOn
};
}
catch (JsonException je)
{
ExceptionLogger.LogInApplicationInsight(je);
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties(),
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
catch (AdalException ae)
{
ExceptionLogger.LogInApplicationInsight(ae);
if (ae.ErrorCode == "failed_to_acquire_token_silently")
{
isAdalException = true;
}
else
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties(),
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
catch (Exception ex)
{
ExceptionLogger.LogInApplicationInsight(ex);
}
if(isAdalException)
{
try
{
string cacheValue = Convert.ToString(cacheManager.get(userUniqueId));
string decryptedCache = CryptographyUtility.Decrypt(cacheValue);
cache = JsonConvert.DeserializeObject<UserTokenCache>(decryptedCache);
UserAssertion pbiAssertion = new UserAssertion(cache.PBIAccessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", UserProperties.UserName);
UserAssertion graphAssertion = new UserAssertion(cache.GraphAccessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", UserProperties.UserName);
cache = null;
powerBIResult = await authContext.AcquireTokenAsync(pbiResourceID, credential, pbiAssertion);
graphResult = await authContext.AcquireTokenAsync(graphResourceId, credential, graphAssertion);
cache = new UserTokenCache
{
GraphAccessToken = graphResult.AccessToken,
PBIAccessToken = powerBIResult.AccessToken,
PBITokenExpires = powerBIResult.ExpiresOn,
GraphTokenExpires = graphResult.ExpiresOn
};
} catch (Exception ex)
{
ExceptionLogger.LogInApplicationInsight(ex);
}
}
return cache;
}
If we use cache.PBIAccessToken and cache.GraphAccessToken to calculate user assertions which are used in AcquireTokenAsync method, it is throwing the error that TenantId is mismatch. In this case, what is the token which needs to be used to calculate UserAssertion.
Generally , we usually use AcquireTokenAsync to get the access token and refresh token the first time user login . After that you could get the token silently(invoke AcquireTokenSilentAsync to get the accessToken ), it will get the token from the TokenCache or silently use refreshToken . If access tokens and refresh tokens both expired ,you may get AdalSilentTokenAcquisitionException( Failed to acquire token silently. Call method AcquireToken) .
Then you could catch that exception , and invokingHttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },OpenIdConnectAuthenticationDefaults.AuthenticationType); to redirect user to azure ad login page , after login , user call api with AcquireTokenSilentAsync will work since access token/refresh token exist in cache ,and not expired. Your code in if(isAdalException) is useless if i understand your requirement correctly .
Recently, I face a Azure authenticate question, the error message as follows:
Additional information: ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
Here is code:
private TokenCloudCredentials credentials;
public ManagerClient(TokenCloudCredentials credentials)
{
this.credentials = credentials;
}
public void GetHostedServices()
{
var computeClient = new Microsoft.WindowsAzure.Management.Compute.ComputeManagementClient(credentials);
var services = computeClient.HostedServices.List().HostedServices;
foreach (var service in services)
{
Console.WriteLine("ServiceName: " + service.ServiceName);
Console.WriteLine("Uri: " + service.Uri);
Console.WriteLine();
}
}
public static TokenCloudCredentials GetCredentials(string subscriptionId = "")
{
var token = GetAccessToken();
if(string.IsNullOrEmpty(subscriptionId))
subscriptionId = ConfigurationManager.AppSettings["subscriptionId"];
var credential = new TokenCloudCredentials(subscriptionId, token);
return credential;
}
private static string GetAccessToken()
{
AuthenticationResult result = null;
var context = new AuthenticationContext(string.Format(
ConfigurationManager.AppSettings["login"],
ConfigurationManager.AppSettings["tenantId"]));
result = context.AcquireToken(
ConfigurationManager.AppSettings["apiEndpoint"],
ConfigurationManager.AppSettings["clientId"],
new Uri(ConfigurationManager.AppSettings["redirectUri"]));
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
return result.AccessToken;
}
var credentials = Authorizator.GetCredentials("37a80965-5107-4f9b-91c6-a1198ee40226");
new ManagerClient(credentials).GetHostedServices();
var computeClient = new Microsoft.WindowsAzure.Management.Compute.ComputeManagementClient(credentials, new Uri("https://management.core.chinacloudapi.cn/"));
Default Uri is: https://management.core.windows.net
so China Azure need to be changed to:***/management.core.chinacloudapi.cn/