I want to create a function which will purge a file on Azure CDN.
Here in the documentation It says How can I purge the content specifying the path.
POST https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Cdn/profiles/{profileName}/endpoints/{endpointName}/purge?api-version=2017-10-12
But the security is provided by Azure Active Directory OAuth2 Flow.
Hence I need to use clientId, secretId (from here https://blogs.msdn.microsoft.com/maheshk/2017/04/01/azure-cdn-how-to-purge-cdn-content-from-c-code/)
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/microsoft.onmicrosoft.com");
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
Task<AuthenticationResult> resultstr = authenticationContext.AcquireTokenAsync("https://management.core.windows.net/", clientCredential);
WebClient client = new WebClient();
//authentication using the Azure AD application
var token = resultstr.Result.AccessToken;
I wander Is there a way to make purge request using storage key and not clientId, secretId?
No, it is not possible. The Azure Rest API Endpoints - Purge Content is integrated with Azure AD authentication, it needs your valid credentials to get the access token.
See this link : Getting Started with REST - Register your client application with Azure AD.
Most Azure services (such as Azure Resource Manager providers and the classic deployment model) require your client code to authenticate with valid credentials before you can call the service's API. Authentication is coordinated between the various actors by Azure AD, and provides your client with an access token as proof of the authentication. The token is then sent to the Azure service in the HTTP Authorization header of subsequent REST API requests. The token's claims also provide information to the service, allowing it to validate the client and perform any required authorization.
Related
Scenario
I have a Dynamics 365 v9 organisation hosted online. I have a set of Azure Functions hosted in an Azure Function App on a different tenant to my Dynamics organisation.
I've created web hooks using the Dynamics Plugin Registration Tool, which at certain events (such as when a Contact is created in Dynamics), POST data to my Azure Functions via their endpoint URLs.
Authentication between Dynamics 365 and my Azure Functions is achieved by passing an x-functions-key value in the HTTP request's authentication HttpHeader.
The Azure Functions receive data from the event in Dynamics in the form of a RemoteExecutionContext which I can read using the following code:
using System.Net;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
var jsonContent = await req.Content.ReadAsStringAsync();
log.Info(jsonContent);
return req.CreateResponse(HttpStatusCode.OK);
}
Question
How can the Azure Function then authenticate back with the calling Dynamics 365 organisation to read and write data?
What I've tried
Xrm Tooling
The simplest way to authenticate would be to use the CrmServiceClient from Microsoft.Xrm.Tooling.Connector.dll. However, I don't necessarily have a username and password to provide the CrmServiceClient's constructor. Perhaps credentials could be passed securely via the HTTP POST request?
Application User
I've tried registering an Application User in Dynamics. I supply the client id and client secret to my Azure Functions, but authentication fails because the user is in a different tenant to my Azure Functions.
Considered Solutions
One object of the received jsonContent string is called ParentContext . Perhaps this can be reused to authenticate back with the calling Dynamics organisation.
Marc Schweigert has recommended using S2S and has provided a sample to his AzureFunctionApp repository. If I can get this approach to work I'll post the solution here.
I wouldn't have thought you can sensibly use the 'real' users credentials to connect to CRM.
I would use a service account to connect back into CRM. Create a new CRM
user especially for this purpose, if you make the user non-interactive you shouldn't consume a license. You can then use the credentials of that service account to connect to CRM using CrmServiceClient. Alternatively have a look at Server to Server authentication.
If you are able to deliver a user id to your Function App, you use the service account to impersonate 'real' users via the CRM web services.
To impersonate a user, set the CallerId property on an instance of
OrganizationServiceProxy before calling the service’s Web methods.
I have done something similar recently, but without relying on the Azure subscription authentication functionality for connecting back into D365. In my case calls were coming to Azure functions from other places, but the connection back is no different. Authentication does NOT pass through in any of these cases. If an AAD user authenticates to your Function application, you still need to connect to D365 using an application user, and then impersonate the user that called you.
First, make sure that the application you registered in Azure AD under App Registrations is of the type "Web app / API" and not "Native". Edit the settings of the registered app and ensure the following:
Take not of the Application ID, which I'll refer to later as appId.
Under "API Access - Required Permissions", add Dynamics CRM Online (Microsoft.CRM) and NOT Dynamics 365.
Under "API Access - Keys", create a key with an appropriate expiry. You can create multiple keys if you have multiple functions/applications connecting back as this "App". I'll refer to this key as "clientSecret" later.
If the "Keys" option isn't available, you've registered a Native app.
I stored the appId and clientSecret in the application configuration section of the Function App, and accessed them using the usual System.Configuration.ConfigurationManager.AppSettings collection.
The below examples use a call to AuthenticationParameters to find the authority and resource URLs, but you could just as easily build those URLs manually using the countless examples online. I find this will just update itself if they ever change, so less work later.
These are simple examples and I'm glossing over the need to refresh tokens and all those things.
Then to access D365 using OData:
string odataUrl = "https://org.crm6.dynamics.com/api/data/v8.2/"; // trailing slash actually matters
string appId = "some-guid";
string clientSecret = "some key";
AuthenticationParameters authArg = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(odataUrl)).Result;
AuthenticationContext authCtx = new AuthenticationContext(authArg.Authority);
AuthenticationResult authRes = authCtx.AcquireTokenAsync(authArg.Resource, new ClientCredential(appId, clientSecret)).Result;
using (HttpClient client = new HttpClient()) {
client.TimeOut = TimeSpan.FromMinutes (2);
client.DefaultRequestHeaders.Add("Authorization", authRes.CreateAuthorizationHeader ());
using (HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, $"{odataUrl}accounts?$select=name&$top=10")) {
using (HttpResponseMessage res = client.SendAsync(req).Result) {
if (res.IsSuccessStatusCode) {
Console.WriteLine(res.Content.ReadAsStringAsync().Result);
}
else {
// cry
}
}
}
}
If you want to access D365 using the Organization service, and LINQ, use the following. The two main parts that took me a while to find out are the format of that odd looking organization.svc URL, and using Microsoft.Xrm.Sdk.WebServiceClient.OrganizationWebProxyClient instead of Tooling:
string odataUrl = "https://org.crm6.dynamics.com/xrmservices/2011/organization.svc/web?SdkClientVersion=8.2"; // don't question the url, just accept it.
string appId = "some-guid";
string clientSecret = "some key";
AuthenticationParameters authArg = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(odataUrl)).Result;
AuthenticationContext authCtx = new AuthenticationContext(authArg.Authority);
AuthenticationResult authRes = authCtx.AcquireTokenAsync(authArg.Resource, new ClientCredential(appId, clientSecret)).Result;
using (OrganizationWebProxyClient webProxyClient = new OrganizationWebProxyClient(new Uri(orgSvcUrl), false)) {
webProxyClient.HeaderToken = authRes.AccessToken;
using (OrganizationServiceContext ctx = new OrganizationServiceContext((IOrganizationService)webProxyClient)) {
var accounts = (from i in ctx.CreateQuery("account") orderby i["name"] select i).Take(10);
foreach (var account in accounts)
Console.WriteLine(account["name"]);
}
}
Not sure what context you get back in your Webhook registration, not tried that yet, but just making sure that there's a bearer token in the Authorization header generally does it, and the two examples above inject it in different ways so you should be able to splice together what's needed from here.
This is something I'm curious about as well but I have not had the opportunity to experiment on this.
For your second option have you registered the application and granted consent in the target AAD?
https://learn.microsoft.com/en-us/dynamics365/customer-engagement/developer/use-multi-tenant-server-server-authentication
When they grant consent, your registered application will be added to the Azure AD Enterprise applications list and it is available to the users of the Azure AD tenant.
Only after an administrator has granted consent, you must then create the application user in the subscriber’s Dynamics 365 tenant.
I believe the root of the access issue is related to the Application's Service Principal Object (the Object local to the target Tenant)
Service Principal Object
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-application-objects#service-principal-object
In order to access resources that are secured by an Azure AD tenant, the entity that requires access must be represented by a security principal. This is true for both users (user principal) and applications (service principal). The security principal defines the access policy and permissions for the user/application in that tenant. This enables core features such as authentication of the user/application during sign-in, and authorization during resource access.
Consider the application object as the global representation of your application for use across all tenants, and the service principal as the local representation for use in a specific tenant.
HTH
-Chris
Using S2S you can use AcquireToken to retrieve the Bearer
var clientcred = new ClientCredential(clientId, clientSecret);
AuthenticationContext authContext = new AuthenticationContext(aadInstance, false);
AuthenticationResult result = authContext.AcquireToken(organizationUrl, clientcred);
token = result.AccessToken;
ExpireDate = result.ExpiresOn.DateTime;
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
My scenario is simple I have a simple Azure Function with B2C authentication on it and I'm writing unit tests but I found an issue, I'm not able to authenticate to the azure functions programmatically.
I'm able to access through the browser and even I can grab the token and put it into the unit test and it works fine, but when I try to generate a token using the ClientID, TenantID, etc. I get a token, but 401 Unauthorized response on the Azure functions.
Is there a way to generate a valid B2C token programmatically (without login in the browser?
The approach I'm using so far:
public static async Task<AuthenticationResult> GetAccessToken(string resourceUri, string clientId, string clientSecret)
{
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
string aadInstance = "https://login.microsoftonline.com/";
string tenant = "<mytenant>.onmicrosoft.com";
string authority = string.Concat(aadInstance, tenant);
AuthenticationContext authContext = new AuthenticationContext(authority);
return await authContext.AcquireTokenAsync(resourceUri, clientCredential);
}
I'm getting a token (EY.......) but is not valid, when I passed to the Azure Function request, it returns 401 Unauthorized.
Thanks in advance!
Ivan
A couple of months ago, Microsoft released a policy for resource owner password credentials flow, with that policy you can simulate a login passing the login details in a query as follows:
Create a ROPC policy in B2C
Register an application
Test the policy as follows:
https://te.cpim.windows.net/{B2C TENANT}/{ROPC B2C POLICY}/oauth2/v2.0/token?username={USERNAME}&password={password}&grant_type=password&scope=openid+{CLIENT ID}+offline_access&client_id=[CLIENT ID]&response_type=token+id_token
You can find more detailed info here
Your unit test is acquiring a token from the Azure AD v1.0 endpoint rather than the Azure AD B2C v2.0 endpoint.
Your Azure function is expecting the token to be issued by the Azure AD B2C v2.0 endpoint.
In the short term, you can consider acquiring the token from the Azure AD B2C v2.0 endpoint by replaying the browser requests using the HttpClient class.
In the near term, support for the resource owner password credential grant by Azure AD B2C will enable your unit test to acquire a token from the Azure AD B2C v2.0 endpoint by POSTing a user credential to the endpoint.
I've posted this before, but the thread became pretty extensive and confusing and a resolution was never met. I'm reposting with a clear and concise block of code and my desired outcome.
I'm looking to use client-flow authentication for an Azure App Services backend.
I'd like to use MSAL, to support both Microsoft Accounts (MSA) and AAD accounts. Been stuck on this for weeks with no resolution in sight.
PublicClientApplication myApp = new PublicClientApplication("registered-app-id-in-apps.dev-portal");
string[] scopes = new string[] { "User.Read" };
AuthenticationResult authenticationResult = await myApp.AcquireTokenAsync(scopes);
JObject payload = new JObject();
payload["access_token"] = authenticationResult.AccessToken;
payload["id_token"] = authenticationResult.IdToken;
user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
Why doesn't this work?
What do I have to do to get it to work?
Getting a 401 exception, tried with MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory as well as MobileServiceAuthenticationProvider.Microsoftaccount
--App Service Auth Config for Microsoft Account:
ClientID and ClientSecret as it appears in apps.dev.microsoft.com
--App Service Auth Config for AAD:
ClientID as it appears in apps.dev.microsoft.com
Issuer URL: https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
Client Secret: (Blank)
Been having the same issue, having set up Azure Active Directory authentication on the App Service & attempting to authenticate from a WinForms client using MSAL. Turns out that, as of the time of this writing, Azure App Service does not support AAD V2 (including MSAL). Found the below note here:
At this time, AAD V2 (including MSAL) is not supported for Azure App Services and Azure Functions. Please check back for updates.
So ADAL seems to be the only viable option at the moment, unless you handle the authentication inside your backend code yourself.
I am using OWIN OpenID Connect Middleware to connect to Azure AD. I am able to authenticate the user successfully and redirect back to callback endpoint. I am a bit confused here as i am receiving only id_token & code in the response.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "Azure AD - TEST",
Caption = "azure AD",
SignInAsAuthenticationType = signInAsType,
ClientId = "some guid",
Authority = "https://sts.windows.net/idp",
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
RedirectUri = "https://localhost:44392/ExternalLogins/Callback/",
AuthenticationMode = AuthenticationMode.Active,
});
Callback Method :
[HttpPost]
[Route("ExternalLogins/Callback")]
[AllowAnonymous]
public async Task<IHttpActionResult> ExternalLoginCallback()
{
var content = await Request.Content.ReadAsStringAsync();
// I could see the content is a string with id_token, code , state etc.
//id_token is a JWT, so i can decode it and see the user claims and use them later
}
My Questions are :
Is Azure AD used for only authenticating the user ? What about authorizatoin ?
If i want to make calls to other APIs after authentication, how do i do that as i don't have access_token ?
I think i can exchange code with access_token but not sure which Azure endpoint i need to call to get access_token ?
What is the difference between AuthenticationMode.Active and AuthenticationMode.Passive ?
Azure AD can absolutely authorize a user and get your Access/Refresh tokens. It supports all oAuth 2.0 and OIDC flows.
You'll need to get an access token to make calls to an api. Let's say you want to call a /get endpoint on the MS Graph, you will stuff the access token into the body of the http request with the keyword Bearer ey... in front of it.
Additionally, you'll need to go into the Azure Portal and configure the delegated permissions you want to access.
The auth code is used to exchange for the access_token. I suggest checking out this protocol doc that shows you how to use all the endpoints. The short answer is you POST to the /token endpoint.
The difference between active and passive is a bit complex for a SO answer, I recommend reading this blog post about the differences.
I'll just add that if you want to see some sample code using Azure AD you can go to Azure AD Dev Guide or Azure AD code samples on Github.
I have an Owin based Web App and a backend Web API, they are authenticated against AAD and the workflow can be describe as below listed.
Web App authenticates end users against AAD using Federation Authentication.
Web App requests a JWT from AAD for accessing the backend Web API.
The main code for authenticating end users.
public void ConfigureAuth(IAppBuilder app)
{
// other code...
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
}
The main code for getting JWT for accessing the backend API:
internal async Task<string> GetAccessToken()
{
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync(apiId, credential);
// Here, what I wanted is to use the other overloaded method
// authContext.AcquireTokenAsync(apiId, credential, userAssertion);
// But to instantiate a UserAssertion instance, the only way is
// to use the constructor new UserAssertion(assertionString)
// and the assertionString should be in JWT format
// unfortunately, the assertionString from Ws-Federation auth is
// for sure in SAML2 format. So, the question is:
// Give I am using Ws-Federation auth protocal, How can I pass the
// user information in requesting a JWT to backend API resource?
return result.AccessToken;
}
Generally, the whole authentication workflow is OK, I can both authenticate end users and get JWT for accessing backedn APIs. But the problem is that there is no end user claims in the JWT. I am sure I should get users claims from the federation authentication result and then put them in the process of requesting the JWT. Unfortunately, with all methods, libraries and classes I didn't find a solution to do that.
BTW, https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect gives an example how to obtain a JWT with end user claims included, but the solution does not work with my scenario as I am using Federation authentication rather than OpenID Connect.
Edit
To make the question clear: in the web app, I would like to request a JWT token for accessing the backend web api by using the method AuthenticationContext.AcquireTokenAsync.
From my demo code, you can see I am using the AcquireTokenAsync(apiId, clientCredential) overloaded verion. But this version does not attach the end users claims inside. Actually what I needed is the AcquireTokenAsync(apiId, clientCredential, userAssertion) overloaded method.
However, to instantiate a UserAssertion, I need the user assertion string which is the AccessToken from user authentication result. Unfortunetaly, the UserAssertion class only accept JWT format assertion string, but the Ws-Federation authentication returns the SAML2 format assertion string, so I am not able to instantiate a UserAssertion instance.
So, my question is: given the condition that I am using Ws-Federation authentication protocol for authenticating an end user, in the backend how can I pass the user assertion information (it is in SAML2 format) to AAD for requesting a JWT for a backend api resource?
AAD provides "canned" claims. There are no claims rules to add other attributes to the token.
Refer: Supported Token and Claim Types.
If you want other attributes, you need to use the Graph API.