I have a simple azure function (Authorization level set to Anonymous) with an HTTP trigger
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
return new OkObjectResult("This is a response from secured function.");
}
I want to secure it with Managed Identities, so I turned on system-assigned identity
And in my AD enterprise app registration, I can see created a system-assigned identity so I copied its Application ID value
and for testing purposes, I want to trigger it from another azure function
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
var clientID = {application / client ID of system identity}
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var accessToken = await azureServiceTokenProvider.GetAccessTokenAsync(clientID);
// Call secured azure function
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://my-secured-function.azurewebsites.net");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetAsync("/api/HttpTrigger1");
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
return new OkObjectResult(result);
}
else
{
return new OkObjectResult("Error - " + response.StatusCode);
}
}
}
The code works, it generates & sends a token within the HTTP request. However, the "secured" azure function is still publicly available.
The question is how can I protect the "secured" azure function, so it can be triggered only with an HTTP request with a generated token using managed identities.
The System Assigned Managed Identity you enabled here only gives an identity to your app, through a Service Principal. This is not blocking any access unlike the Firewall/IP Restrictions (that you could use but I assume that you want to rely on Identity only here).
What you are looking for is basically Authentication with Azure AD. From there, you could use the Managed Identity (if this is suitable) of the caller to authenticate against your app through the AAD. That also works with any Service Principal or users.
You need to click "Authentication" on the left panel of your Function app. Then add Microsoft AD as an identity provider.
Add the necessary settings(you can let Azure create an App registration or use the managed identity which you have already created. With this step, you will lock your Azure function app so it is triggered only if a valid AD token is provided to it.
Related
I have a situation where i want to show the Health of my Azure services to the public forum i am trying to architect it something like Azure timer function will get the status of the resources and dump in database which can be displayed by the external App by calling API i tried few stuff but Azure Resource health API is protected by Azure security and which expects the Oauth2 implicit flow which i suppose is initiated in browser and through user interaction is there any way we can bypass this and have simply access the resource health API
In Azure function, we can use Azure MSI to require token then we can call resource health API with the token.
For example
Enable Azure MSI in Azure function
Assign Azure RABC role Contributor role to the MSI
Code
// Install package "Azure.Identity"
private static HttpClient httpClient = new HttpClient();
[FunctionName("Function2")]
public static async Task Run([TimerTrigger("0 */5 * * * *", RunOnStartup=true)]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
var creds = new DefaultAzureCredential();
var scopes = new string[] { "https://management.azure.com/.default" };
AccessToken token= await creds.GetTokenAsync(new TokenRequestContext(scopes));
string uri = " ";
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get,uri)) {
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
using (HttpResponseMessage response = await httpClient.SendAsync(request)) {
if (response.IsSuccessStatusCode) {
var str = await response.Content.ReadAsStringAsync();
log.LogInformation(str);
}
}
}
We have two separeate dotnet core apis(API1 & API2) that are protected using azure ad b2c. Both these apis are registered on the b2c tenant and have their scopes exposed.
We have a client web applicaiton that is to access the above protected apis. This web app has been registered as a applicaiton in b2c tenant and has api permissions set for the above apis with proper scopes defined.
We use MSAL.net with a signinpolicy to sign the user in to the web app.
the authentication call requires scopes to mentioned. So we add API1's scope in the call.
(note : one scope of a single resource can be added in a auth call shown below)
public void ConfigureAuth(IAppBuilder app)
{
// Required for Azure webapps, as by default they force TLS 1.2 and this project attempts 1.0
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebChunkingCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(Globals.WellKnownMetadata, Globals.Tenant, Globals.DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = Globals.ClientId,
RedirectUri = Globals.RedirectUri,
PostLogoutRedirectUri = Globals.RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claim type that specifies the Name property.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidateIssuer = false
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {Globals.ReadTasksScope} {Globals.WriteTasksScope}",
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebCookieManager()
}
);
}
The OnAuthorizationCodeRecieved method in Startup.Auth.cs recieved the code recieved as a result of above auth call and uses it to get a access token based on the scopes provided and stores it in the cache. shown below
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
try
{
/*
The `MSALPerUserMemoryTokenCache` is created and hooked in the `UserTokenCache` used by `IConfidentialClientApplication`.
At this point, if you inspect `ClaimsPrinciple.Current` you will notice that the Identity is still unauthenticated and it has no claims,
but `MSALPerUserMemoryTokenCache` needs the claims to work properly. Because of this sync problem, we are using the constructor that
receives `ClaimsPrincipal` as argument and we are getting the claims from the object `AuthorizationCodeReceivedNotification context`.
This object contains the property `AuthenticationTicket.Identity`, which is a `ClaimsIdentity`, created from the token received from
Azure AD and has a full set of claims.
*/
IConfidentialClientApplication confidentialClient = MsalAppBuilder.BuildConfidentialClientApplication(new ClaimsPrincipal(notification.AuthenticationTicket.Identity));
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
}
catch (Exception ex)
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.BadRequest,
ReasonPhrase = $"Unable to get authorization code {ex.Message}.".Replace("\n", "").Replace("\r", "")
});
}
}
This access token is then used in the TasksController to call AcquireTokenSilent which retrieves the access token from the cache, which is then used in the api call.
public async Task<ActionResult> Index()
{
try
{
// Retrieve the token with the specified scopes
var scope = new string[] { Globals.ReadTasksScope };
IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault()).ExecuteAsync();
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiEndpoint);
// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
// Handle the response
switch (response.StatusCode)
{
case HttpStatusCode.OK:
String responseString = await response.Content.ReadAsStringAsync();
JArray tasks = JArray.Parse(responseString);
ViewBag.Tasks = tasks;
return View();
case HttpStatusCode.Unauthorized:
return ErrorAction("Please sign in again. " + response.ReasonPhrase);
default:
return ErrorAction("Error. Status code = " + response.StatusCode + ": " + response.ReasonPhrase);
}
}
catch (MsalUiRequiredException ex)
{
/*
If the tokens have expired or become invalid for any reason, ask the user to sign in again.
Another cause of this exception is when you restart the app using InMemory cache.
It will get wiped out while the user will be authenticated still because of their cookies, requiring the TokenCache to be initialized again
through the sign in flow.
*/
return new RedirectResult("/Account/SignUpSignIn?redirectUrl=/Tasks");
}
catch (Exception ex)
{
return ErrorAction("Error reading to do list: " + ex.Message);
}
}
The issue is the code recieved by the OnAuthorizationCodeRecieved method can only be used to get the access token for API1 since its scope was mentioned in auth call. When trying to get access token for API2 it returns null.
Question : How to configure the web app so that it is able to access multiple protected apis?
Please suggest.
The code can be found from the sample https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi
A single access token can only contain scopes for a single audience.
You have 2 options:
Combine both services into a single app registration and expose different scopes.
Request multiple tokens - one per service. If your SSO policy is configured correctly in B2C, this should happen silently unbeknownst to the user.
I recommend using option 1 if you own both services (which it sounds like you do). A few tips related to this option.
When declaring the scopes in the combined app registration, use the dot-syntax {LogicalService}.{Operation}. If you do this, the scopes will be grouped by logical service within the Azure portal.
Make sure you are validating scopes in your service. Validating only the audience is not good enough and would allow an attacker to make lateral movements with a token bound for another service.
I am using http trigger azure function in my App Service. I want this http trigger azure function should not accessible publicly and accessible only from the App Service.
Currently I have created host key for http trigger function and I am using this for authenticate request.
Which authentication method should I use for this? Any thoughts.
Azure Function:
public static class RemoveSubscriptionsForPayers
{
[FunctionName(nameof(RemoveSubscriptionsForPayers))]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
[Inject] ILoggingService loggingService,
[Inject] ICommandValidator commandValidator,
[Inject] ICommandHandler<ResultDto,RemoveSubscriptionsForPayersCommand> commandHandler)
{
var logger = new Logger(loggingService);
try
{
IActionResult actionResult = null;
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
logger.Info($"RemoveSubscriptionsForPayersCommand received on {nameof(RemoveSubscriptionsForPayers)}");
var command = requestBody.AsPoco<RemoveSubscriptionsForPayersCommand>();
if (commandValidator.Validate<RemoveSubscriptionsForPayersCommand>(req, command, new RemoveSubscriptionsForPayersCommandValidator(), logger, ref actionResult))
{
var response =await commandHandler.HandleAsync(command, logger);
actionResult = new OkObjectResult(response);
}
return actionResult;
}
catch (Exception ex)
{
logger.Error($"Exception while processing {nameof(RemoveSubscriptionsForPayers)}", ex,
nameof(RemoveSubscriptionsForPayers));
throw;
}
}
}
You can use Azure AD to authenticate your functions, which is more secure.
After opening Azure AD authentication, you need to obtain an access token.
Please open Azure active directory in the Azure portal and find App registrations, you need to search for the function you registered in Azure AD in the search box.
you need to find the parameter values of the url and body to obtain the token here.
URL to get access token
Body:
client_id
scope
username and password
grant_type: The value of grant_type is fixed, both are password.
client_secret
You can get Token like this:
Now you can use the access token of Azure AD to access your functions.
The request header name is Authorization, and the header value is Bearer <access-token>:
we got an application deployed as App Service and we are using SignalR for communication. After enabling AAD authentication - in browsers we started receiving 302 responses with redirect location to Azure AD.
Seems like the authentication layer on App Service is ignoring access_token passed by query string.
Request
Request URL: wss://<url>/hubs/chat?access_token=<token>
Request Method: GET
Response
Status Code: 302 Redirect
Location: https://login.windows.net/common/oauth2/authorize?...
After looking everywhere we couldn't find any solution to make this work.
The only solution to this issue that we see is either to disable authentication on App Service or use Long-Pooling, but both options are not acceptable in our situation.
By default, you web application will not get the access token from query string. Commonly, it will get the access token from authorization header or the cookie.
To get the access token from query string, you need to implement your custom authentication way.
Install Microsoft.Owin.Security.ActiveDirectory NuGet package.
Create an authentication provider which will get access token from query string.
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var value = context.Request.Query.Get("access_token");
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
return Task.FromResult<object>(null);
}
}
Add map in .
app.Map("/yourpath", map =>
{
map.UseWindowsAzureActiveDirectoryBearerAuthentication(new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Provider = new QueryStringOAuthBearerProvider(),
Tenant = tenantId,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = clientId
}
});
map.RunSignalR(hubConfiguration);
});
After multiple calls with Microsoft Technical Support, MS confirmed that App Service Authentication layer doesn't support access token passed in query string and there are no plans for this support yet. So there are two options:
Use different protocol for SignalR (long pooling works just fine)
Drop App Service Authentication
Using a custom middleware, I was able to update the request prior to authorization occurring:
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace Stackoverflow.Example.Security.Middleware
{
public class BearerTokenFromQueryToHeaderMiddleware
{
private readonly RequestDelegate _next;
public BearerTokenFromQueryToHeaderMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var token = context.Request.Query["access_token"];
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", $"Bearer {token}");
}
await _next(context);
}
}
}
I didn't try to get this working with the OpenID framework, but I did test using a custom policy. As long as this is registered earlier than the authentication, then this middleware should execute prior to the framework looking for the token in the header.
I'm trying to implement OAuth using OWIN for a Web API v2 endpoint on my local intranet. The API is hosted in IIS using built-in Windows Authentication. In short, this is what I want to happen.
When I ask for my Token at /token
Pull the WindowsPrincipal out of the OWIN context
Use the SID from the WindowsPrincipal to look up some roles for this
user in a SQL table.
Create a new ClaimsIdentity that stores the username and roles
Turn that into a Json Web Token (JWT) that I sent bak
When I request a resource from my API using my token
Convert the JWT Bearer token back to the ClaimsIdentity
Use that ClaimsIdentity for authorizing requests to the resource by
role
This way I don't have to do a database lookup for user roles on each
request. It's just baked into the JWT.
I think I'm setting everything up correctly. My Startup.Configuration method looks like this.
public void Configuration(IAppBuilder app)
{
// token generation
// This is what drives the action when a client connects to the /token route
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
// for demo purposes
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(8),
AccessTokenFormat = GetMyJwtTokenFormat(),
Provider = new MyAuthorizationServerProvider()
});
//// token consumption
app.UseOAuthBearerAuthentication(
new OAuthBearerAuthenticationOptions()
{
Realm = "http://www.ccl.org",
Provider = new OAuthBearerAuthenticationProvider(),
AccessTokenFormat = GetMyJwtTokenFormat()
}
);
app.UseWebApi(WebApiConfig.Register());
}
MyAuthorizationServerProvider looks like this...
public class MyAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Since I'm hosting in IIS with Windows Auth enabled
// I'm expecting my WindowsPrincipal to be here, but it's null :(
var windowsPrincipal = context.OwinContext.Request.User.Identity;
// windowsPrincipal is null here. Why?
// Call SQL to get roles for this user
// create the identity with the roles
var id = new ClaimsIdentity(stuff, more stuff);
context.Validated(id);
}
}
My problem is that context.Request.User is null here. I can't get to my WindowsPrincipal. If I create some other dummy middleware, I can get to the WindowsPrincipal without issue. Why is it null in this context? Am I doing something wrong?
Swap the order of UseOAuthAuthorizationServer and UseOAuthBearerAuthentication. UseOAuthBearerAuthentication calls UseStageMarker(PipelineStage.Authenticate); to make it (and everything before it) run earlier in the ASP.NET pipeline. User is null when you run during the Authenticate stage.