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 .
Related
I am having a C# MVC web application through which I'm trying to read the user's group using Microsoft Graph API. But when I'm trying to do so through code using HttpClient I'm getting "403 Forbidden" error.
I have all the required permissions but still getting the error, can't get the reason for the error or any solution for it. I even tried to google it but couldn't find anything.
If anyone can help.
try
{
using (var httpClient = new HttpClient(HttpClientHelper.GetWinHttpHandler()))
{
var json = #"{ 'securityEnabledOnly': true }";
var stringContent = new StringContent(json);
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + graphapitoken);
httpClient.BaseAddress = new Uri("https://graph.microsoft.com/");
var response = Task.Run(() => httpClient.PostAsync($"v1.0/users/" + UsermailId + "/getMemberGroups", new StringContent(json, Encoding.UTF8, "application/json")));
response.Wait();
if (response.Result.IsSuccessStatusCode)
{
string strResponse = await response.Result.Content.ReadAsStringAsync();
object dec = JsonConvert.DeserializeObject(strResponse);
JObject obj = JObject.Parse(dec.ToString());
List<JToken> obj1 = obj["value"].ToList();
listAssociatedGroups = obj1.Values<string>().ToList();
}
}
}
Getting Token
public class Token
{
public static string GetToken()
{
return GraphToken(ConfigurationManager.AppSettings["ida:Tenant"],ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"]);
}
private static string GraphToken(string tenantId, string clientId, string clientSecret)
{
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId);
ClientCredential credential = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = authContext.AcquireTokenAsync("https://graph.microsoft.com", credential).GetAwaiter().GetResult();
return result.AccessToken;
}
public static string TokenAsync(string tenantId, string clientId, string clientSecret, string resourceURI)
{
try
{
var authenticationContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}");
ClientCredential credential = new ClientCredential(clientId, clientSecret);
var authenticationResult = authenticationContext.AcquireTokenAsync(resourceURI, credential).GetAwaiter().GetResult();
return authenticationResult.AccessToken;
}
catch (Exception ex)
{
throw new Exception("Failed to retrive AAD token");
}
}
}
API Permissions I have
First, you could test this API with Graph Explorer directly.
POST https://graph.microsoft.com/v1.0/me/getMemberGroups
{
"securityEnabledOnly": true
}
I'm not sure which kind of flows that you used to get access token in your code. If you use client credentials flow, you need to add one of the application permissions. Delegated permissions can be used for the other flows.
Just a stab in the dark:
Some of those permissions require admin consent.
Does your application have admin consent for those permissions?
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;
}
}
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
I use Facebook C# SDK v 6 and enabled "Remove Offline Access" on my application settings
and after login and get the access token, I am trying to exchange for long lived token(60days one)
I am unable to get it as both tokens expiration is with in 24 hrs.
Here is my code
For log in to Facebook
private const string Scope = "publish_stream,manage_pages";
FacebookClient _fb = new FacebookClient();
var fbLoginUrl = _fb.GetLoginUrl(
new
{
client_id = AppId,
client_secret = Appsecret,
redirect_uri = RedirectUri,
response_type = "code",
scope = Scope,
state = state
});
To get short lived access token
if (Request.QueryString["code"] != null)
code = Request.QueryString["code"];
var result = _fb.Post("oauth/access_token",
new
{
client_id = AppId,
client_secret = Appsecret,
redirect_uri = RedirectUri,
code = code,
scope = Scope,
response_type="token"
});
To get long lived access token
var result1 = _fb.Post("oauth/access_token",
new
{
client_id = AppId,
client_secret = Appsecret,
grant_type = "fb_exchange_token",
fb_exchange_token= Session["fb_access_token"] as string
});
This will do.
var result = GetExtendedAccessToken("my_short_lived_old_token");
var extendedToken = result.access_token;
public dynamic GetExtendedAccessToken(string oldToken)
{
dynamic result = new ExpandoObject();
try
{
var api = new FacebookClient
{
AccessToken = oldToken,
AppId = ClientID,
AppSecret = ClientSecret
};
dynamic parameters = new ExpandoObject();
parameters.grant_type = "fb_exchange_token";
parameters.fb_exchange_token = oldToken;
parameters.client_id = ClientID;
parameters.client_secret = ClientSecret;
result = api.Get("oauth/access_token", parameters);
}
catch (FacebookOAuthException err)
{
result.error = "Error";
result.message = err.Message;
}
catch (Exception err)
{
result.error = "Error";
result.message = err.Message;
}
return result;
}