I am currently building an Office365 Website which manages your files within onedrive and your sharepoint files. Currently though I am having an issue in that if the sharepoint isn't a root address one (i.e. https://mysharepoint.sharepoint.com) but is a seperate one (i.e. https://intergendev1.sharepoint.com/anothersite or https://intergendev1.sharepoint.com/sites/moarsite) I cannot acquire the token silently, in other words I cannot use my token from the Office API to acquire a token for the sharepoint site. My theory is that I am using the incorrect ResourceId or ServiceEndpointUri, currently I just use https://intergendev1.sharepoint.com/anothersite for both, and I am un-able to aquire the token, but it DOES work for https://intergendev1.sharepoint.com/.
Thanks for the help in advance. If it helps, here is my AquireToken code:
internal static async Task<SharepointApiClient> GetExtenalSharepoint(Uri serviceEndpointUri,
string serviceResourceId)
{
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.AzureADAuthority, new AdTokenCache.AdTokenCache(signInUserId));
try
{
return new SharepointApiClient(serviceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(serviceResourceId,
new ClientCredential(SettingsHelper.ClientId,
SettingsHelper.AppKey),
new UserIdentifier(userObjectId,
UserIdentifierType.UniqueId));
return authResult.AccessToken;
})
{
ResourceId = serviceResourceId
};
}
catch (AdalException exception)
{
//Partially handle token acquisition failure here and bubble it up to the controller
if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
authContext.TokenCache.Clear();
throw exception;
}
return null;
}
}
Related
I am trying to create SAS policy for azure service bus namespace using shared access policy connectionstring in .NET Core 2.1.
I can create it using Microsoft.Azure.Management.ServiceBus NuGet package as follows
private static async Task<string> GetToken()
{
try
{
// Check to see if the token has expired before requesting one.
// We will go ahead and request a new one if we are within 2 minutes of the token expiring.
if (tokenExpiresAtUtc < DateTime.UtcNow.AddMinutes(-2))
{
Console.WriteLine("Renewing token...");
var tenantId = appOptions.TenantId;
var clientId = appOptions.ClientId;
var clientSecret = appOptions.ClientSecret;
var context = new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}");
var result = await context.AcquireTokenAsync(
"https://management.core.windows.net/",
new ClientCredential(clientId, clientSecret)
);
// If the token isn't a valid string, throw an error.
if (string.IsNullOrEmpty(result.AccessToken))
{
throw new Exception("Token result is empty!");
}
tokenExpiresAtUtc = result.ExpiresOn.UtcDateTime;
tokenValue = result.AccessToken;
Console.WriteLine("Token renewed successfully.");
}
return tokenValue;
}
catch (Exception e)
{
Console.WriteLine("Could not get a new token...");
Console.WriteLine(e.Message);
throw e;
}
}
private static async Task CreateSASPolicy()
{
try
{
if (string.IsNullOrEmpty(namespaceName))
{
throw new Exception("Namespace name is empty!");
}
var token = await GetToken();
var creds = new TokenCredentials(token);
var sbClient = new ServiceBusManagementClient(creds)
{
SubscriptionId = appOptions.SubscriptionId,
};
List<AccessRights?> list = new List<AccessRights?> { AccessRights.Send };
var AuthRule = new SBAuthorizationRule { Rights = list };
var authorizationRuleName = "SendRule"; //policy name
Console.WriteLine("Creating SAS policy...");
var result = sbClient.Namespaces.CreateOrUpdateAuthorizationRuleAsync(resourceGroupName, namespaceName, authorizationRuleName, AuthRule).Result;
Console.WriteLine("Created SAS policy successfully.");
}
catch (Exception e)
{
Console.WriteLine("Could not create a SAS policy...");
Console.WriteLine(e.Message);
throw e;
}
}
But for above code I need to give at least "Azure Service Bus Data Owner" to the app which we are using to create token.
I have also tried using http client as follows
using (HttpClient httpclient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
{
httpclient.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", "", accessToken))));
string baseAddress = #"https://management.core.windows.net/<subscriptionId>/services/ServiceBus/namespaces/<namespace>/AuthorizationRules/";
var sendRule = new SharedAccessAuthorizationRule("contosoSendAll",
new[] { AccessRights.Send });
var result = await httpclient.GetAsync(baseAddress).ConfigureAwait(false);
if (result.IsSuccessStatusCode)
{
var response = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
}
}
It returns 403(Forbidden).
Question :-
Is there any way to create SAS policy without giving "Azure Service Bus Data Owner" permission to app? How?
Can create SAS policy using shared access policy connectionstring? How?
Can create SAS policy by using current login user credentials? How?
Can create SAS policy using shared access policy connectionstring? How?
If you use shared access policy connectionstring to create SAS policy, we just can create policy for topic or subscription. For the namespace, we need to implement it with Azure Resource provider API. It the way you are currently using. For more details, please refer to here.
Regarding how to create policy fro the topic or queue, we can use the package Azure.Messaging.ServiceBus.Administration.
For example
ServiceBusAdministrationClient client = new ServiceBusAdministrationClient(connectionString);
QueueProperties queue =await client.GetQueueAsync("myqueue");
queue.AuthorizationRules.Add( new SharedAccessAuthorizationRule(
"manage",
new[] { AccessRights.Manage, AccessRights.Send, AccessRights.Listen })
);
queue= await client.UpdateQueueAsync(queue);
foreach (var rule in queue.AuthorizationRules) {
Console.WriteLine(rule.KeyName);
}
Is there any way to create SAS policy without giving "Azure Service Bus Data Owner" permission to app? How?
When we use Azure Resource Provider API to create the resource, we should have the right Azure RABC permissions. About creating policy, we need to have permissions Microsoft.ServiceBus/namespaces/authorizationRules/action and Microsoft.ServiceBus/namespaces/authorizationRules/write. If you do not want to use Azure Service Bus Data Owner, you can create a custom role with these permissions. For more details, please refer to here and here.
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
So, here is the problem. I searched all over the MSDN and here on Stack but there isn't one definitive answer how to get (or is it even possible as of today?) to access Shared calendars in Office365.
I followed this tutorial and here is offending method:
public async Task<ActionResult> Index()
{
List<MyCalendar> myCalendars = new List<MyCalendar>();
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.AppKey), new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
var discoveryCapabilitiesResult = await discClient.DiscoverCapabilitiesAsync();
var dcr = await discClient.DiscoverCapabilityAsync("Calendar");
OutlookServicesClient exClient = new OutlookServicesClient(dcr.ServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(dcr.ServiceResourceId, new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey), new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
//var calendarsResult = await exClient.Me.Calendars.ExecuteAsync();
var calendarsResult = await exClient.Me.Calendars.ExecuteAsync();
do
{
var calendars = calendarsResult.CurrentPage;
foreach (var c in calendars)
{
myCalendars.Add(new MyCalendar { DisplayName = c.Name });
}
calendarsResult = await calendarsResult.GetNextPageAsync();
} while (calendarsResult != null);
}
catch (AdalException exception)
{
//handle token acquisition failure
if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
authContext.TokenCache.Clear();
//handle token acquisition failure
}
}
return View(myCalendars);
}
This function will return only Calendar under "My Calendars" but not the others (see the picture)
I get only the first one - that is mine calendar. Second one under "My Calendars" is shared with me (I'm not the author, somebody else is and I have r/w on it) and one under "Shared Calendars" is company-wide one (on this one I also have r/w permissions).
Is there a way to get all of them? On portal.azure.com my app is added and I set permissions for both mine and shared calendars:
I have no idea what to do. Contacts are working properly but I cannot find a way to get any shared calendar.
Based on the test, the Microsoft Graph could get the all calendars. Here is the rest for your reference:
Shared calendar from chenchenLi to NanYu:
Query the calendars from NanYu:
More detail about the Microsoft Graph REST to get the calendars, you can refer the link below:
List calendars
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
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).