I’m trying to create an .NET console application to authenticate users for an Azure function app. I want to authenticate users using their AD credentials and then create a token based on that. I believe I need to enable “Public client flows” in order to achieve this. I’m pretty new to this, but after many failed attempts I managed to get it working after setting the Application ID URI and the scope in the “Expose an API” section. I believe the manifest refers to this property as “identifierUris”. An according to some findings both public access and identifierUris cannot be used at the same time.
Is there an alternative way to achieve this? Any explanations or reasonings as to why this is not ideal would be appreciated as well.
This is the code we're using to retrieve the token and use it:
var publicClient = PublicClientApplicationBuilder
.Create(clientId)
.WithAuthority(authorityUri)
.WithRedirectUri(redirectUri)
.Build();
var accessTokenRequest = publicClient.AcquireTokenInteractive(scopes);
var accessToken = await accessTokenRequest.ExecuteAsync();
restRequest.AddHeader("authorization", "Bearer " + token);
This is the Pulumi code creating the Azure AD Application, where functionApp is the Pulumi.Azure.AppService.FunctionApp that we are trying to authorize against:
var azureApp = new AzureAD.Application(name, new AzureAD.ApplicationArgs
{
DisplayName = name,
AvailableToOtherTenants = false,
Homepage = "https://VisualStudio/SPN",
Oauth2AllowImplicitFlow = true,
ReplyUrls = { "http://localhost" },
IdentifierUris =
{
functionApp.DefaultHostname.Apply(dnsName => "https://" + dnsName)
},
PublicClient = true
}, new CustomResourceOptions {DependsOn = functionApp});
When PublicClient is set to false, this deploys fine. When it is set to true, the the underlying API call returns a 400 error with this text:
Property identifierUris is invalid
So we set PublicClient to false and then manually update it to true in the portal, which works fine:
What are we missing?
You don't need to set PublicClient to true because it applies to ROPC flow, Device Code Flow or Windows Integrated Auth flow as your screenshot shows.
But according to your code, I think you are not using any the three auth flows.
You should create a new app registration which represents the API/server side (your Azure function app) and do "Expose an API".
Add the scope/permission (exposed by the API/server side) in your app registration which represents the client side (.NET console application).
To use PublicClientApplicationBuilder, you just need to modify "allowPublicClient": true in the manifest file of the app registration of the client side. Don't add any IdentifierUris or set the Application ID URI and the scope in the "Expose an API" section because this should be done in the app registration which represents the API/server side.
Related
I've been leveraging Azure Function Apps to automate items in Azure. I currently have working functions that connect to Microsoft Graph, Resource Explorer, KV etc. using service principal / OAUTH client credentials flow (inside the function app). To call my function app, I've implemented implicit flow. While I'm not an expert at OAUTH, I am familiar enough now to get this configured and working.
However, there are Azure endpoints I need to use that don't support using a service principal token, they only support an actual AAD user requesting a token. Here's one that I want to run: Create synchronizationJob
If you look at the permissions section of the above link, you'll see that "application" is not supported. I did test this in a function: I can run these endpoints in Graph Explorer fine (as myself), but they fail in the function when using a token linked to a service principal.
Since this new automation is going to be an Azure Function (and not an interactive user), I can't use the authorization code flow. I need this service account's OAUTH to be non-interactive.
TL;DR
I can run the above endpoint in Azure's Graph Explorer just fine:
Azure Graph Explorer
since I'm authenticating as myself, and have a token generated based on my user ID. But for automating using Azure Functions where I need to use this endpoint (which doesn't support OAUTH using an SP), I need some way to have a back-end AAD user auth and pull a token that can be used to run the endpoint.
Any help is welcome! Feel free to tell me that I'm either missing something very basic, or not understanding a core principal here.
As juunas mentioned no guarantee that will work though, I test in my side and it seems doesn't work although I assigned "Global administrator" role to the service principal.
For your situation, you can request the access token in your function code and then use the access token to request the graph api.
Add the code like below in your function to get access token.
HttpClient client = new HttpClient();
var values = new Dictionary<string, string>
{
{ "client_id", "<your app client id>" },
{ "scope", "<scope>" },
{ "username", "<your user name>" },
{ "password", "<your password>" },
{ "grant_type", "password" },
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://login.microsoftonline.com/<your tenant id>/oauth2/v2.0/token", content);
var responseString = await response.Content.ReadAsStringAsync();
var obj = JObject.Parse(responseString);
var accessToken = (string)obj["access_token"];
And then use the access token got above to request graph api.
I'm experimenting with various Azure features and currently want to retrieve a secret from KeyVault.
Straight to the case:
I'm using this nuget package to interact with my azure resources.
I've developed a simple .NET Core console app and run it locally.
I have a KeyVault resource with one secret defined which is active and not expired.
I've registered an App in AAD so my locally shipped .NET Core console app has an identity within AAD.
Than I've created a "client secret" within this registered app in AAD to use it to authenticate myself as an app.
After that I've added access policy in my KeyVault resource to allow GET operation for secrets for this registered app:
Then I've developed a small piece of code which should retrieve the desired secret:
public class AzureAuthentication
{
public async Task<string> GetAdminPasswordFromKeyVault()
{
const string clientId = "--my-client-id--";
const string tenantId = "--my-tenant-id--";
const string clientSecret = "--my-client-secret--";
var credentials = new ClientSecretCredential(tenantId, clientId, clientSecret);
var client = new SecretClient(new Uri("https://mykeyvaultresource.vault.azure.net"), credentials);
var secret = await client.GetSecretAsync("admincreds");
return secret.Value.Value;
}
}
However when I'm trying to do this I'm getting an AccessDenied error:
Am I missing something painfully obvious here? Or there is some latency (>30 min for this moment) for which changes from Access policies screen in KeyVault resource are applied?
I test your code and Get permission, it works fine.
From your screenshot, it looks you didn't add the correct service principal related to the AD App to the Access policies.
If you add the service principal related to the AD App, it will appear as APPLICATION, not COMPOUND IDENTITY.
So when you add it, you could search for the client Id(i.e. application Id) or the name of your App Registration directly, make sure you add the correct one.
Make sure your AD App(service principal) has the correct permission in your keyvault -> Access policies
I have an Azure Function with Authorization/authnetication enabled via AD log in.
I am trying to authenticate by generating a token using client_credentials:
Refer to the following code below:
var tokenendpoint = "https://login.microsoftonline.com/172f05a2-f956-4856-b4c8-9580a54dbd56/oauth2/token";
string clientID = "eaeff78a-26ef-4bcb-b977-638316ff15b7";
string clientSecret = "HvVlipQkpuezmD4YiUcWVpZ5Cn1cP3vxiW61pSpDo8k=";
string resource = "eaeff78a-26ef-4bcb-b977-638316ff15b7"; //ClientID
string grantType = "client_credentials";
using (var reqToken = new WebClient())
{
NameValueCollection parameters = new NameValueCollection();
parameters.Add("client_id", clientID);
parameters.Add("client_secret", clientSecret);
parameters.Add("resource", resource);
parameters.Add("grant_type", grantType);
var responseTokenBytes = reqToken.UploadValues(tokenendpoint, "POST", parameters);
string responseTokenContent = Encoding.UTF8.GetString(responseTokenBytes).Replace(#"\", "");
azureFunctionTokenResponse = responseTokenContent.Deserialize<AzureFunctionTokenResponseBase>();
AzureFunctionToken = azureFunctionTokenResponse.access_token;
}
All works fine if I set the resource as the ClientID of my function.
However, in many examples online the Resource is set to the Azure Function Uri.
If I set my Resource to https://www.xxxxxx.azurewebsites.com then I get a 401 error.
Why is this?
I spent a whole day in getting this to finally work but nowhere in the docs does it say to enter the ClientID as the Resource??
If you use the same AAD app to enable Authorization/Authentication for your Azure Function and your client code to acquire the access_token for accessing your Azure Function, you could specify the resource to the Application ID (ClientID) or the App ID URI of your AAD app.
In general, we would use the ClientID as the resource, and App Service Authorization/Authentication would compare the Client ID you configured under Authentication / Authorization > Azure Active Directory Settings with the aud property of the incoming JWT bearer token, you could leverage https://jwt.io/ to decode your token.
However, in many examples online the Resource is set to the Azure Function Uri.
If I set my Resource to https://www.xxxxxx.azurewebsites.com then I get a 401 error.
I assume that those samples may use the App ID URI, you could set the App ID URI to https://www.xxxxxx.azurewebsites.com for your AAD app (Settings > Properties > App ID URI), then you could use App ID URI for the resource parameter.
Note: For this approach, you may need adjust the Azure Active Directory Settings for your Azure Function, you may keep the Client ID to the Application ID of your AAD app and add App ID URI to ALLOWED TOKEN AUDIENCES list or you could just replace it with your App ID URI.
Additionally, you could ADAL library for acquiring the token. Also, if you create each AAD app for your Azure Function and your client app, you could follow this issue.
I've almost configured my OpenId owin authentication/authorization in Azure Active Directory. My configuration is the following:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieName = "AppServiceAuthSession"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = _authority,
PostLogoutRedirectUri = PostLogoutRedirectUri,
RedirectUri = PostLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
},
AuthorizationCodeReceived = async context =>
{
var id = new ClaimsIdentity(context.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(context.AuthenticationTicket.Identity.Claims);
var appToken = "MyToken";
id.AddClaim(new Claim("MyTokenKey", appToken));
context.AuthenticationTicket = new AuthenticationTicket
(
new ClaimsIdentity(id.Claims, context.AuthenticationTicket.Identity.AuthenticationType),
context.AuthenticationTicket.Properties
);
}
},
});
But I want to add one more application token (not user token) to claims list to be able to have ability to use this token in any place on my site. Also it's good point for me that I don't need to get this token from my external token provider more then one time per an authentication session.
But place, where I'm going to add my logic (AuthorizationCodeReceived as well as other methods from OpenIdConnectAuthenticationNotifications) is called only when I use my local IIS(run locally), when I try to use azure IIS, this method has not been called at all. In this case my User is authenticated anyway, but this method and the similar methods from OpenIdConnectAuthenticationNotifications(except RedirectToIdentityProvider) are not fired.
I've downloaded the git source code of Katana project and referenced this project to my instead of the official nuget packages to debug its and as I think currently, I've found the reason why it happens. The AuthorizationCodeReceived "event" method is called from OpenIdConnectAuthenticationHandler class in AuthenticateCoreAsync method. But also, the calling of this method is required that the below checking must give the true result:
if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(Request.ContentType) // May have media/type; charset=utf-8, allow partial match.
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
&& Request.Body.CanRead)
{
//some necessary preparation to call `AuthorizationCodeReceived` event method
}
As we can see, this checking allows only POST requests and I see these POST requests when I run app in local IIS, but I cannot see these POST requests when I deploy my application in azure portal (I've debugged both of options : on local IIS and in azure portal).
As summary from the above, this is the only one difference between these runnings. (Azure IIS doesn't send POST request at all by some reason).Any other methods in Katana project (which I checked) are called in the same way.
Could anybody help with it?
PS Note, I check any changes only after clearing of browser data (cache/history and so on).
The answer is the following:
The authorization in azure portal should be configured as shown above. In case if you chose LogIn with Azure Active Directory, then app services auth takes place outside of your app, and the custom authorization is not triggered.
I've setup an Azure Mobile App Service backend and there is a Xamarin app consuming it's services. It uses custom authentication in the Azure Mobile App Service for registering and authenticating the users of the app.
For local development/debugging application's OWIN startup class contains some code to setup the authentication options of the app service, as described in https://azure.microsoft.com/nl-nl/documentation/articles/app-service-mobile-dotnet-backend-how-to-use-server-sdk/#local-debug.
In Azure the Mobile App Service's authentication is enabled (Authentication / Authorization) setting the 'Action to take when request is not authenticated' option to 'Allow request (no action)' so the application handles the request authentication.
This all works as desired.
Now we would like to support a custom domain on our Mobile App Service and support the current ourmobileappservice.azurewebsites.net domain. We've configured the custom domain, configured it's SSL certificate and all works well. New tokens are issued with the custom domain as audience/issuer and it's also validated in this manor.
But when issuing a token with ourmobileappservice.azurewebsites.net as audience/issuer, it's rejected during token validation. It seems only our custom domain is allowed as valid audience.
For local development we're specifying the app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions { ... }), also setting the ValidAudiences property. So I wanted to use this setup for the Azure environment as well, so we can specify multiple valid audiences for token validation. Note: of course the AppServiceAuthenticationOptions is different than for local development, e.g. SigningKey comes from Azure's environment variables).
Unfortunately Azure doesn't seem to use this at all. It still keeps failing in the exact same way. As you can see only the custom domain is specified as valid audience:
Warning JWT validation failed: IDX10214: Audience validation
failed. Audiences: 'https://ourmobileappservice.azurewebsites.net/'.
Did not match: validationParameters.ValidAudience:
'https://ourcustom.domain.com/' or
validationParameters.ValidAudiences: 'null'.
How can I configure the Azure Mobile App Service with custom authentication setup so it's valid audiences supports both the custom domain as the previous ourmobileappservice.azurewebsites.net?
Edit
The valid audiences are specified for Azure as follows:
public static void ConfigureMobileApp(IAppBuilder app)
{
...
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { "https://ourcustom.domain.com/", "https://ourmobileappservice.azurewebsites.net/" },
ValidIssuers = new[] { "https://ourcustom.domain.com/", "https://ourmobileappservice.azurewebsites.net/" },
TokenHandler = config.GetAppServiceTokenHandler()
});
...
}
You can sign the token with any URL which you like in your custom auth provider. Whatever you specify in the AppServiceLoginHandler.CreateToken() method will go into the JWT.
When it comes to validation of the token, if you're debugging locally the list of URLs specified in the middleware will be used to validate the audience and issuer. In production Azure will magically use your default Azure domain and custom domains.
The way I did it was to create a new web.config AppSetting which contains the valid signing URLs for all environments.
<add key="ValidUrls" value="https://api.myproductiondomain.com/, https://myproductionapp.azurewebsites.net/, http://localhost:59475/" />
In the Startup.MobillApp.cs I'm populating the valid audiences and issuers from this list.
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
if (string.IsNullOrEmpty(settings.HostName))
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
var validUrls = ConfigurationManager.AppSettings["ValidUrls"].Split(',').Select(u => u.Trim()).ToList();
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = validUrls,
ValidIssuers = validUrls,
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Now in my login method before generating the token, I'm checking that the hostname of the current request is in the same AppSetting whitelist. If it's valid use the current hostname as the Audience and Issuer for my token.
Something like this;
// Get current URL
var signingUrl = $"{this.Request.RequestUri.Scheme}://{this.Request.RequestUri.Authority}/";
// Get list from AppSetting
var validUrls = ConfigurationManager.AppSettings["ValidUrls"].Split(',').Select(u => u.Trim()).ToList();
// Ensure current url is in whitelist
if (!validUrls.Contains(signingUrl))
{
return this.Request.CreateUnauthorizedResponse();
}
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
};
var signingKey = this.GetSigningKey();
// Sign token with this
var audience = signingUrl;
var issuer = signingUrl;
// Set expirey
var expiry = TimeSpan.FromHours(72);
// Generate token
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(
claims,
signingKey,
audience,
issuer,
expiry
);
We actually just made a set of updates which allow you to set audiences in the portal. If you return to the AAD settings under App Service Authentication / Authorization, you should see some new options under the "Advanced" tab. This includes an editable list of allowed token audiences.
If you add https://ourmobileappservice.azurewebsites.net to that list, you should be good to go.