Error occuriring with call to user authenticated http trigger - azure

I am using Azure Functions v3
I am trying to use Authentication and I have set my function to User level security for its HttpTriggers
The logic below is called on the startup of my function
protected override void SetupAuthentication(
IServiceCollection services, IConfiguration configuration)
{
var tokenOptions = configuration.GetSection("JwtIssuerOptions")
.Get<TokenConfiguration>();
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = tokenOptions.SecurityKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = tokenOptions.Issuer,
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = tokenOptions.Audience,
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
services.Configure<IdentityConfiguration>(configuration.GetSection("IdentityConfiguration"));
services.AddScoped<CustomJwtBearerEvents>();
services
.AddAuthentication(o =>
{
o.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = tokenValidationParameters;
options.EventsType = typeof(CustomJwtBearerEvents);
});
}
When I call the function externally I get the error
No authentication handler is registered for the scheme 'WebJobsAuthLevel'.
The registered schemes are: Bearer. Did you forget to call AddAuthentication().AddSomeAuthHandler?.
What have I missed?
I need to mimic the same convention as web apps
[FunctionName("GetPayments")]
public async Task<List<PaymentDto>> GetPaymentsAsync(
[HttpTrigger(AuthorizationLevel.User, "post", Route = "payments/get-payments")]
HttpRequest req,
ILogger log)
{
var data = await req.ReadAsStringAsync();
//THis is where I have my logic which I only want to be able to access if the user has permissions
}
I have seen the link below
https://damienbod.com/2020/09/24/securing-azure-functions-using-azure-ad-jwt-bearer-token-authentication-for-user-access-tokens/comment-page-1/?unapproved=127819&moderation-hash=3fdd04b596812933c4c32e8e8c8cf26a#comment-127819
It initially looked to be what I need, but I cant work out how to adapt it so that it just uses the identity token validation side
Any help would be appreciated
Paul

Have a look at: https://www.nuget.org/packages/DarkLoop.Azure.Functions.Authorize
The latest version ensures all built-in authentication is in place before you add your own or extend the built-in ones. By the way JTW Bearer is already configured by the Functions runtime.
All you need to do is call in your method
services.AddFunctionsAuthentication();
services.AddFunctionsAuthorization();
and then chain whatever other schemes you need to configure after the AddFunctionsAuthentication() call. The resulting authentication builder is designed to handle modifications to the jwt bearer (AddJwtBearer(options => ...) and will not break telling you the Bearer scheme already exists.
This package also gives you the ability to use the FunctionsAuthorize attribute to handle granular authorization requirements for your HTTP functions. Here is a blog post with details: https://blog.darkloop.com/post/functionauthorize-for-azure-functions-v3

Related

Azure Functions app + Auth0 provider, getting 401 when calling API with auth token

I have read, and implemented local dev projects to match, Auth0's Complete Guide To React User Authentication with Auth0, successfully. I am confident in the implementation, given that all aspects of login and route protection are working correctly, as well as the local express server successfully authenticating API calls that use authentication tokens generated via the Auth0 React SDK.
I have added third button to the sample project's external-apis.js view for use in calling another API that I am trying to integrate with, which is an Azure Functions app. I would like to use Auth0 for this API in the same way I do for the express server, and take advantage of Azure's "Easy Auth" capabilities, as discussed in this MS doc. I have implemented an OpenID Connect provider, which points to my Auth0 application, in my Azure Function app per this MS doc.
This is what the function that calls this Azure Function app API looks like:
const callAzureApi = async () => {
try {
const token = await getAccessTokenSilently();
await fetch(
'https://example.azurewebsites.net/api/ExampleEndPoint',
{
method: 'GET',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${token}`,
},
}
)
.then((response) => response.json())
.then((response) => {
setMessage(JSON.stringify(response));
})
.catch((error) => {
setMessage(error.message);
});
} catch (error) {
setMessage(error.message);
}
};
My issue is that making calls to this Azure Function app API always returns a 401 (Unuthorized) response, even though the authorization token is being sent. If I change the Authorization settings in the Azure portal to not require authentication, then the code correctly retrieves the data, so I'm confident that the code is correct.
But, is there something else I have missed in my setup in order to use Auth0 as my authentication provider for the backend in Azure?
Through continued documentation and blog reading, I was able to determine what was missing from my original implementation. In short, I was expecting a little too much after reading about tge "Easy Auth" features of Azure, at least when using an OpenID Connect provider like Auth0. Specifically, the validation of the JSON Web Token (JWT) does not come for free, and needed further implementation.
My app is using the React Auth0 SDK to sign the user in to the identity provider and get an authorization token to send in its API requests. The Azure documentation for client-directed sign-in flow discusses the ability to validate a JWT using a specific POST call to the auth endpoint with the JWT in the header, but even this feature seems out of reach here, given that OpenID Connect is not listed in the provider list, and my attempts at trying it anyway continued to yield nothing but 401s.
The answer, then, was to implement the JWT validation directly into the Azure function itself, and return the proper response only when the JWT in the request header can be validated. I would like to credit blog posts of Boris Wilhelm and Ben Chartrand for helping to get to this final understanding of how to properly use Auth0 for an Azure Functions backend API.
I created the following Security object to perform the token validation. The static nature of the ConfigurationManager is important for caching the configuration to reduce HTTP requests to the provider. (My Azure Functions project is written in C#, as opposed to the React JS front-end app.)
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
namespace ExampleProject.Common {
public static class Security {
private static readonly IConfigurationManager<OpenIdConnectConfiguration> _configurationManager;
private static readonly string ISSUER = Environment.GetEnvironmentVariable("Auth0Url", EnvironmentVariableTarget.Process);
private static readonly string AUDIENCE = Environment.GetEnvironmentVariable("Auth0Audience", EnvironmentVariableTarget.Process);
static Security()
{
var documentRetriever = new HttpDocumentRetriever {RequireHttps = ISSUER.StartsWith("https://")};
_configurationManager = new ConfigurationManager<OpenIdConnectConfiguration> (
$"{ISSUER}.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever(),
documentRetriever
);
}
public static async Task<ClaimsPrincipal> ValidateTokenAsync(AuthenticationHeaderValue value) {
if(value?.Scheme != "Bearer")
return null;
var config = await _configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameter = new TokenValidationParameters {
RequireSignedTokens = true,
ValidAudience = AUDIENCE,
ValidateAudience = true,
ValidIssuer = ISSUER,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKeys = config.SigningKeys
};
ClaimsPrincipal result = null;
var tries = 0;
while (result == null && tries <= 1) {
try {
var handler = new JwtSecurityTokenHandler();
result = handler.ValidateToken(value.Parameter, validationParameter, out var token);
} catch (SecurityTokenSignatureKeyNotFoundException) {
// This exception is thrown if the signature key of the JWT could not be found.
// This could be the case when the issuer changed its signing keys, so we trigger
// a refresh and retry validation.
_configurationManager.RequestRefresh();
tries++;
} catch (SecurityTokenException) {
return null;
}
}
return result;
}
}
}
Then, I added this small bit of boilerplate code toward the top of any HTTP-triggered functions, before any other code is run to process the request:
ClaimsPrincipal principal;
if ((principal = await Security.ValidateTokenAsync(req.Headers.Authorization)) == null) {
return new UnauthorizedResult();
}
With this in place, I finally have the implementation I was looking for. I'd like to improve the implementation with something more generic like a custom attribute, but I'm not sure that's possible yet either for OpenID Connect providers. Still, this is a perfectly acceptable solution for me, and gives me the level of security I was looking for when using a React front-end with an Azure Functions back-end.
Cheers!

Validate a JWT from another C# ASP.NET Core 3.1 API that consumes it

So I am getting a JWT from a web call that comes from Service A to secure Service B with the same token. Unfortunately it uses a third party library so I don't know the exact key it issues but I can see it's payload just fine. Is there a way I can make my service know the token is okay somehow? I tried this in Startup.ConfigureServices
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ")?.Last() ?? string.Empty;
if (!string.IsNullOrEmpty(accessToken))
context.Token = accessToken;
return Task.CompletedTask;
}
};
});
Now this gets the JWT I expect and in a test case let's say this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmNvbSJ9.XzkQ9hQ0JyhQDpFZ00Ukc_5ickUmjxVUBvlMrcEeycw
There is nothing wrong here and the 'context.Token' is getting set, but when I do this:
[Authorize(AuthenticationSchemes = "Bearer")]
[Route("[controller]/[action]")]
public class JobsUpdateController : ControllerBase
I get a 401 no matter what when using the '[Authorize(AuthenticationSchemes="Bearer")]'. Is there anyway I can do a custom authorize? Else I was thinking of doing some long form of setting my own authentication method and maybe making a custom attribute. But I was hoping I could just get the startup working for this if I know the 'issuer' and several other keys in the payload of what I expect.
You can configure JWT validation in options.TokenValidationParameters.
This is NOT secure, and you're basically allowing pretty much any token. But regardless, here it is:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(
options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, // `iss` claim
ValidIssuers = new []{"https://localhost:1234"},
ValidateAudience = false, // `aud` claim
ValidateLifetime = false, // `exp`, `nbf` claims
ValidateIssuerSigningKey = false, // signature
SignatureValidator = (token, parameters) => new JwtSecurityToken(token), // don't validate signature
};
});

Azure Identity: Custom token validation to validate issuers in a multi tenant app

I followed Microsofts article to implement my own issuer validation ("Customizing token validation" is the headline of the section).
This seemed to work with JWT-Tokens issued in an app-only context, but failed when the first call to my API was a JWT token issued through user delegation.
I found that this line of code is causing the problem:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration);
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
await existingOnTokenValidatedHandler(context);
// Your code to add extra configuration that will be executed after the current event implementation.
options.TokenValidationParameters.ValidIssuers = new[] { /* list of valid issuers */ };
options.TokenValidationParameters.ValidAudiences = new[] { /* list of valid audiences */};
}
});
This is the original code from the link I posted above.
I implemented my own issuer validation in the following way:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.TokenValidtionParameters.RoleClaimType = "roles";
options.Events.OnTokenValidated = async context =>
{
await existingOnTokenValidatedHandler(context);
options.Authority = "https://login.microsoftonline.com/common";
var validTenants = FileTenantStore.Tenants.Select(x => x.AzureAdTenantId).ToList();
options.TokenValidationParameters.ValidIssuers = GetValidIssuers(validTenants);
options.TokenValidationParameters.IssuerValidator = ValidateIssuers;
};
});
I have a multi tenant app, so I need to let only some tenants pass and reject the most.
This solution behaves a little strange:
Calling the API with an App-Only token works always.
Calling the API with a Delegated token fails with the following error message and does not even jump into the callback:
Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException:
IDW10303: Issuer:
'https://login.microsoftonline.com/{OUR_TENANT_ID}/v2.0',
does not match any of the valid issuers provided for this application.
at Microsoft.Identity.Web.Resource.AadIssuerValidator.Validate(String
actualIssuer, SecurityToken securityToken, TokenValidationParameters
validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateIssuer(String
issuer, JwtSecurityToken jwtToken, TokenValidationParameters
validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken
jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String
token, TokenValidationParameters validationParameters, SecurityToken&
validatedToken)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
So in this case "OnTokenValidated" is never called.
Calling the API with an App-Only token on the first time, and then later with a delegated token works fine.
I could fix this problem with moving the lines in the "OnTokenValidated"-Callback one level above:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.TokenValidationParameters.RoleClaimType = "roles";
var validTenants = FileTenantStore.Tenants.Select(x => x.AzureAdTenantId).ToList();
options.TokenValidationParameters.ValidIssuers = GetValidIssuers(validTenants);
options.TokenValidationParameters.IssuerValidator = ValidateIssuers;
options.Events.OnTokenValidated = async context =>
{
await existingOnTokenValidatedHandler(context);
options.Authority = "https://login.microsoftonline.com/common";
};
});
I could even remove the callback "OnTokenValidated" now, but this does not feel right, because of the Microsoft article that is giving clear instructions.
Can I do it like this, or is my solution a security problem?
If you are in netcore>3 could you use AddMicrosoftIdentityWebApiAuthentication extension method. Set subscribeToOpenIdConnectMiddlewareDiagnosticsEvents to true and enable debug logging to look at the event that terminate the check for your particular token.
Based on your code :
services.AddMicrosoftIdentityWebApiAuthentication(Configuration,
jwtBearerScheme: JwtBearerDefaults.AuthenticationScheme,
subscribeToJwtBearerMiddlewareDiagnosticsEvents: true);

Multiple AuthProvider Servicestack

I am having some issue to make work 2 auth provider at the same time for servicestack.
I am using the : JWT Tokens - Allowing users to authenticate with JWT Tokens I am my users get authenticate fine.
Still Now I would like to use the API Keys - Allowing users to authenticate with API Keys for a few external 3rd Parties user access.
Still when I Configure both my users allready authenticate by JWT Tokens doesnt work anymore.
Here is my configuration AuthProvider configuration :
IAuthProvider[] providers = new IAuthProvider[]
{
new JwtAuthProviderReader(this.AppSettings)
{
HashAlgorithm = "RS256",
PrivateKeyXml = this.AppSettings.GetString("TokenPrivateKeyXml"),
PublicKeyXml = this.AppSettings.GetString("TokenPublicKeyXml"),
RequireSecureConnection = this.AppSettings.Get<bool>("TokenUseHttps"),
EncryptPayload = this.AppSettings.Get<bool>("TokenEncryptPayload"),
PopulateSessionFilter = (session, obj, req) =>
{
ApplicationUserSession customSession = session as ApplicationUserSession;
if (customSession != null)
{
customSession.TimeZoneName = obj["TimeZoneName"];
customSession.Type = (FbEnums.UserType) (obj["UserType"].ToInt());
if (Guid.TryParse(obj["RefIdGuid"], out Guid result))
{
customSession.RefIdGuid = result;
}
}
},
},
new ApiKeyAuthProvider(AppSettings)
{
RequireSecureConnection = false
}
};
I am genereting fine the token with JwtAuth. Still It look like servicestack is not accepting my token as a valid session, because now whenever I do :
var session = httpReq.GetSession();
session.IsAuthenticated --> is always FALSE
If my remove ApiKeyAuthProvider from the configuration, token from JwtAuth working fine again.
How do I make both provider works together and tell servicestack tham some users will use JwtAuth and others ApiKeyAuth ?
You need to call a Service that requires Authentication, e.g. has the [Authenticate] attribute in order to trigger pre-Authentication for the IAuthWithRequest providers like JWT and API Key AuthProviders.

Skipping home realm discovery with Ws-Federation OWIN Middleware

Our Mvc/WebAPI solution currently has four trusted identity providers which we have registered in ADFS3. Each of these identity providers can be used by our users by direct links, effectively working around any home-realm-cookies that ADFS may have created (eg: www.ourportal.com/accounts/facebook or www.ourportal.com/accounts/twitter). Currently we are migrating from WIF to OWIN but will keep using WS-Federation protocol for the time being by implementing wsfederation and cookie authentication middleware. When using WIF, we did the following in order to go directly to a known identity provider:
var signInRequest = new SignInRequestMessage(stsUrl, realm) { HomeRealm = homeRealm };
return new RedirectResult(signInRequest.WriteQueryString());
This seems to have two concerning behaviors, it does not pass the WsFedOwinState parameter, and on the return back to the Relying Party, the Home.cshtml is built (with a windows principal) before the the Owin authentication middleware is fired. The Home.cshtml being fired before the Owin middleware is the most concering as this view relies on Claims that would is provided in the transformation done by the authentication pipeline, which is fired afterwards and thus our view does not work. It works in the correct order when going to the portal in the normal way (eg www.ourportal.com)
I understand that in order to provide the Whr parameter, you do the following when configuring the ws-federation middleware:
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.Whr = "SomeUrnOfAnIdentityProvider";
return Task.FromResult(0);
}
but this sets a single identity provider for the whole solution and does not allow our users to go directly to one of a list of identity providers.
The non-working method which builds the sign-in-request is currently:
private RedirectResult FederatedSignInWithHomeRealm(string homeRealm)
{
var stsUrl = new Uri(ConfigurationManager.AppSettings["ida:Issuer"]);
string realm = ConfigurationManager.AppSettings["ida:Audience"];
var signInRequest = new SignInRequestMessage(stsUrl, realm)
{
HomeRealm = homeRealm
};
HttpContext.Request.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return new RedirectResult(signInRequest.WriteQueryString());
}
The ws-federation and cookie middleware are configured as the first middleware in OWIN startup and the default authentication is set to
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
I think I found a solution. The new method for skipping the home realm screen would be like this :
private void FederatedSignInWithHomeRealm(string homeRealm)
{
HttpContext.Request
.GetOwinContext()
.Authentication
.SignOut(CookieAuthenticationDefaults.AuthenticationType);
var authenticationProperties = new AuthenticationProperties { RedirectUri = "/" };
authenticationProperties.Dictionary.Add("DirectlyToIdentityProvider", homeRealm);
HttpContext.GetOwinContext().Authentication.Challenge(authenticationProperties);
}
And the OWIN WS-Federation middleware would be configured like this :
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
Notifications = new WsFederationAuthenticationNotifications()
{
RedirectToIdentityProvider = notification =>
{
string homeRealmId = null;
var authenticationResponseChallenge = notification.OwinContext
.Authentication
.AuthenticationResponseChallenge;
var setIdentityProvider = authenticationResponseChallenge != null
&& authenticationResponseChallenge.Properties
.Dictionary
.TryGetValue("DirectlyToIdentityProvider", out homeRealmId);
if (setIdentityProvider)
{
notification.ProtocolMessage.Whr = homeRealmId;
}
return Task.FromResult(0);
}
},
MetadataAddress = wsFedMetadata,
Wtrealm = realm,
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = realm
}
});

Resources