Developing Azure functions locally - azure

I am leading a large team of azure functions developers. So, most of the examples quoted by Microsoft using the azure web interface don't work for me. I am developing Azure functions locally using emulators to save some costs. I publish all my functions through visual studio into my integration environment.
I am developing a bunch of azure functions that need the api gateway to handle the authentication workflows using Azure AD B2C. Now, there's no api gateway emulator or an Azure AD B2C emulator that I can run locally. My authentication workflows involve intercepting requests to the api, redirecting them to AD B2C for authentication and the subsequent addition of the auth-token to the http header and then invoking the http-triggered azure functions.
Now, the question becomes, how do I test authentication workflows?
How can I setup the api gateway to register my functions running locally in visual studio as api endpoint for my api gateway in the cloud?

Here is another alternative if you are developing a SPA that uses Azure-AD or Azure B2C via Easy Auth, which will do your JWT token validation for you and leaving you to do the following:
Your SPA is going to get a token even locally so do the following:
Inject the ClaimPrincipal into your function
Check if the user is authenticated (e.g., principal.Identity.IsAuthenticated) and return UnauthorizedResult if they are not.
Check for an issuer claim. If the principal has one, it went through Express Auth., your JWT token was validated by it and you can get your claims from it immediately.
If there is no issuer, it's local development and you can turn to the header and pull the JWT token out yourself and get your claims. You could also IFDEF this out for conditional build so that your doubly sure that it's local development.
Here is some example code of pulling the JWT token out of the header (HttpRequest is injected into each function):
private JwtSecurityToken ReadJwtTokenFromHeader(HttpRequest req)
{
if (req.Headers.ContainsKey("Authorization"))
{
var authHeader = req.Headers["Authorization"];
var headerValue = AuthenticationHeaderValue.Parse(authHeader);
var handler = new JwtSecurityTokenHandler();
return handler.ReadJwtToken(headerValue.Parameter);
}
return null;
}
Note: This requires the System.IdentityModel.Tokens.Jwt NuGet package to use JwtSecurityTokenHandler.

Taking #David-Yates's answer I substituted Principal when running locally
module Debug = begin
open System.IdentityModel.Tokens.Jwt
open System.Net.Http.Headers
open System.Security.Claims
let setPrincipalFromBearerToken (log : ILogger) (req : HttpRequest) =
log.LogInformation ("Reading Authorization header")
let success, authHeader = req.Headers.TryGetValue("Authorization")
if not success
then log.LogWarning ("Authorization header missing")
else
match Seq.tryExactlyOne authHeader with
| None -> log.LogWarning ("Authorization header has 0 or more than 1 value")
| Some headerValue ->
let headerValue = AuthenticationHeaderValue.Parse(headerValue);
log.LogInformation ("Authorization header succesfully parsed")
let handler = new JwtSecurityTokenHandler();
let token = handler.ReadJwtToken(headerValue.Parameter);
log.LogInformation ("JWT succesfully parsed")
let identity =
ClaimsIdentity(
req.HttpContext.User.Identity,
token.Claims)//,
//Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme),
//"oid", "roles")
let principal = ClaimsPrincipal(identity)
req.HttpContext.User <- principal
let userIdClaim =
principal.Claims
|> Seq.where (fun c -> c.Type = "oid") // TODO: Use const from MS package if possible
|> Seq.head
log.LogInformation ("Principal succesfully updated, user ID '{0}'", userIdClaim.Value)
end
let isLocal = String.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID"))
if isLocal then Debug.setPrincipalFromBearerToken log req

What I did:
Added an "authorize" API that handles general-purpose authorization against foreign authorities. This API returns my own JWT with my own custom claims that lasts for a some limited amount of time.
Changed all of my other API's to use my custom JWT.
Advantages:
Super easy to test locally. I just add #if DEBUG sections to the authorization API to skip normal authorization and give me a JWT of my design.
I can put whatever I want in the claim, so I use it as a cache to reduce external authorization calls.

Related

How can we secure API to serve only whitelisted clients? Communication between Azure function and Web API

I am using the below design to secure communication between Azure Function and Web API
Step 1 - Request token from AD
Step 2 - Use token to request web api
Code to call the API
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var endpoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT");
var identity_header = Environment.GetEnvironmentVariable("IDENTITY_HEADER");
var resource = "4df52c7e-3d6f-4865-a499-cebbb2f79d26"; //how to secure this ID
var requestURL = endpoint + "?resource=" + resource + "&api-version=2019-08-01";
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("X-IDENTITY-HEADER", identity_header);
HttpResponseMessage response = await httpClient.GetAsync(requestURL);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
var access_token = JsonConvert.DeserializeObject<TokenResp>(responseBody).access_token;
var APIURL = "https://frankapp.azurewebsites.net";
HttpClient callAPI = new HttpClient();
callAPI.DefaultRequestHeaders.Add("Authorization","Bearer "+ access_token);
HttpResponseMessage APIResponse = await callAPI.GetAsync(APIURL);
return new OkObjectResult(APIResponse.StatusCode);
}
Question
The solution works as planned, However, I see a security loophole here. That is, any azure function that has the above code or resource id can call this API!!!
How can I solve this security issue? How can I make only listed azure functions to call the API?
There are several solutions to secure the API App, as mentioned in the comment, you could validate the token via the claims, use the access restriction rules, etc.
From your code, it uses the MSI(managed identity) to get the token for the AD App of the API App, then uses the token to call the API. In this case, I recommend you to use User assignment required setting to restrict the access of the API App, after doing the steps below, just the MSI of the function can get the token for the API App, no need to do anything else.
1.Navigate to the AD App of your API App in the Azure Active Directory in the portal -> click the Managed application in local directory -> Properties -> set the User assignment required to Yes.
2.Create a security group in AAD and add the MSI service principal as a member to it, then add the group to the Users and groups, then the MSI will also be able to call the function.(For the user, it can be added directly, MSI is different, you need to use this nested way, or leverage the App role)
After the steps above, just the MSI added to the Users and groups can get the token successfully and call the API, there are two similar issues I have answered, here and here. In the two posts, they want to secure the function app, in your case, it is the same logic.
Security between Azure Function and API app using AAD can be done in multiple ways:
Claims in access token
Whitelist all the IP range where azure function is hosted, deny others.
Users and group policy in AAD as security group.
Put App service and AF in a single VNET (though that restricts multi-region)
Object ID verification
Read more: https://www.tech-findings.com/2020/01/securing-function-app-with-azure-active-directory.html

How do I authorize a service to call an API using Azure Active Directory?

I have a service that gets an access token from Azure AD. I have an API that I would like to accept that token as authorization.
My service code to call the API is
HttpClient client = new HttpClient()
{
BaseAddress = new Uri("https://localhost:44372/")
};
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, client.BaseAddress + "api/todolist");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await client.SendAsync(request);
The response I get back is a 401 - Unauthorized.
I have a feeling that the issue is in the API ConfigureServices function; specifically (this was taken from an example, so I don't really know what it means yet):
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.TokenValidationParameters.RoleClaimType = "roles";
});
I'm new to Azure and authentication in general so I don't know what options are available or appropriate. I also am not sure how to set up the applications in Azure to allow this. I have the application id of the service set up as an Authorized client application of the API; it is also listed int the knownClientApplications in the API manifest.
There are just so many knobs to turn, I have no idea where to go from here. If anyone can let me know some things to try, that would be outstanding.
Thanks
Here is a code sample on how to call a web API in an ASP.NET Core web app using Azure AD:
https://learn.microsoft.com/en-us/samples/azure-samples/active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore/calling-a-web-api-in-an-aspnet-core-web-application-using-azure-ad/
This sample contains a web API running on ASP.NET Core 2.0 protected by Azure AD. The web API is accessed by an ASP.NET Core 2.0 web application on behalf of the signed-in user. The ASP.NET Web application uses the OpenID Connect middleware and the Active Directory Authentication Library (ADAL.NET) to obtain a JWT bearer token for the signed-in user using the OAuth 2.0 protocol. The bearer token is passed to the web API, which validates the token and authorizes the user using the JWT bearer authentication middleware.

MS Identity Azure app registered but sends unauthorized_client in implicit flow

I have registered an app in Azure for Microsoft Identity platform. I configured it to allow MS Accounts (e.g. outlook.com) and have basically done everything in a few of the quickstarts online here and here (except for "add credentials to your web app"). I have also checked the boxes that enable implicit flow.
I redirect my React application to the URL to sign in (using implicit flow), I get to enter my username but then I see
unauthorized_client: The client does not exist or is not enabled for consumers. If you are the application developer, configure a new application through the App Registrations in the Azure Portal at https://go.microsoft.com/fwlink/?linkid=2083908
Like I mentioned above, I've gone through several quick starts and read about implicit flow here and followed their examples for my code.
I also tried just deleting the app registration and starting over. No luck.
JS Code attempting to implement Implicit Flow
JS code that redirects the browser to a Url that looks like Microsoft's first example on their implicit flow page
goSignIn() {
const tenant = 'common'; // (for us with MS accounts)
const clientId = '*****';
const redir = encodeURIComponent('http://localhost:3000/signin');
const nonce = Math.round(Math.random() * 10000, 0);
const uriTemplate = 'https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?client_id={clientId}&response_type=id_token+token&redirect_uri={redirect}&scope=openid&response_mode=fragment&state={state}&nonce={nonce}';
const filledTemplate = uriTemplate
.replace('{tenant}', tenant)
.replace('{clientId', clientId)
.replace('{redirect}', redir)
.replace('{nonce}', nonce)
.replace('{state}', nonce);
console.log(filledTemplate);
window.location = filledTemplate;
}
App Configuration in Azure:
Azure -> Identity -> App Registrations -> MyApp -> Authentication
Redirect Uri: http://localhost:3000/signin (React app runs on 3000 and I have a route configured for /signin)
Not using any suggested Redirects.
Checked Implicit checkboxes for ID Token and Access Token
Live SDK support enabled
Supported account types is set to "Accounts in any organizational directory and personal Microsoft accounts (e.g. Skype, Xbox, Outlook.com)"
Azure -> Identity -> App Registrations -> MyApp -> API Permissions
MS Graph
User.Read
Email
Profile
openid
From the docs I read, I thought I had done enough to the id token. I'm not sure what tweak must be made in order to get it to work.
I experienced an issue like this one. The mistake I made has to do with the App ID: when you create the client secret the Azure UI will present the secret and the secret ID. This secret ID is not the one to use in your app's configuration. Rather, you need the Application ID found on the Overview page.
I imagine that there are many configuration problems which can produce this error message. In general: pay close attention to the App ID, if the error is that the app is not found.
It seems that you have done enough to get the token. I have tested this on my side, it works well. Here I provide you with my screenshot for you to check again.
Also, here is my working request url, you can login with your msa to have a test.
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id=5fd66168-7ba3-4bbc-a155-bff662eed9f7
&response_type=id_token+token
&redirect_uri=http://localhost:3000/signin
&scope=openid
&response_mode=fragment
&state=12345
&nonce=678910

Context.UserIdentifier is null after switching to Azure SignalR Service

My SignalR Hub requires users to be authenticated and the following line gives me user's Id:
var userId = Context.UserIdentifier;
Similarly, I can get the whole set of claims with:
var user = Context.User.Claims;
This works fine if I use SignalR locally but if I switch to Azure SignalR Service, I get a null value in Context.UserIdentifier. I also get no claims.
Here's what I'm changing in my Startup.cs:
In the ConfigureServices() method, I use
services.AddSignalR().AddAzureSignalR(Configuration["AzureSignalR:ConnectionString"]);
instead of
services.AddSignalR();
And in the Configure() method, I use:
app.UseAzureSignalR(routes =>
{
routes.MapHub<Hubs.MyHub>("/chat");
});
instead of:
app.UseSignalR(routes =>
{
routes.MapHub<Hubs.MyHub>("/chat");
});
Do I need anything else in Azure SignalR configuration to make sure I get user's claims? Any idea why this one change prevents claims from coming through?
P.S. I'm using Azure AD B2C for user authentication and as I said, if I use SignalR locally, everything works which means the code that handles grabbing JWT token from QueryString is working fine.
SignalR service will automatically inherit the claims from your authenticated user, no special configuration is needed. I just tried Azure AD B2C sample with SignalR service and the claims can be get from HubCallerContext.
Could you please check the SignalR access token returned from negotiation to see whether the claims are returned from server at the first place? (Decode it from base64 then you'll see the claims)

Service to service authentication in Azure without ADAL

I configured azure application proxy for our on-premise hosted web service and turned on Azure AD authentication. I am able to authenticate using ADAL but must find a way to get the token and call web service without ADAL now (we are going to use this from Dynamics 365 online and in sandbox mode I can't use ADAL). I followed some examples regarding service to service scenario and I successfully retrieve the token using client credentials grant flow. But when I try to call the app proxy with Authorization header and access token, I receive an error "This corporate app can't be accessed right now. Please try again later". Status code is 500 Internal server error.
Please note the following:
I don't see any error in app proxy connectors event log.
I added tracing on our on-premise server and it seems like the call never comes there.
If I generate token with ADAL for a NATIVE app (can't have client_secret so I can't use client credentials grant flow), I can call the service.
I created an appRole in manifest for service being called and added application permission to the client app.
This is the way I get the token:
public async static System.Threading.Tasks.Task<AzureAccessToken> CreateOAuthAuthorizationToken(string clientId, string clientSecret, string resourceId, string tenantId)
{
AzureAccessToken token = null;
string oauthUrl = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenantId);
string reqBody = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&resource={2}", Uri.EscapeDataString(clientId), Uri.EscapeDataString(clientSecret), Uri.EscapeDataString(resourceId));
HttpClient client = new HttpClient();
HttpContent content = new StringContent(reqBody);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
using (HttpResponseMessage response = await client.PostAsync(oauthUrl, content))
{
if (response.IsSuccessStatusCode)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AzureAccessToken));
Stream json = await response.Content.ReadAsStreamAsync();
token = (AzureAccessToken)serializer.ReadObject(json);
}
}
return token;
}
AzureAccessToken is my simple class marked for serialization.
I assume it must be something I haven't configured properly. Am I missing some permissions that are required for this scenario?
Any help is appriciated.

Resources