Our system is role based and makes a lot of use of the Authorize attribute
In our App Services we use the code below to set this up:
public void ConfigureAuth(IApplicationBuilder app)
{
if (Configuration.GetValue<bool>("UseLoadTest"))
{
app.UseMiddleware<ByPassAuthMiddleware>();
}
app.UseAuthentication();
}
How can I do this with an Azure function Http Trigger?
There is no UseAuthentication() method on IFunctionsHostBuilder.
public override void Configure(IFunctionsHostBuilder builder)
{
}
I'm using .NET Core 3.1.
AFAIK Functions doesn't have such an attribute currently. This issue tracks the work going in to support Filters for Functions but this is still in preview.
Based on the above preview feature, looks like this is one library - dark-loop/functions-authorize - that adds support for this.
I started using Middleware for this: Took inspiration from this article:
https://jinalkumarpatel.hashnode.dev/azure-functions-middleware-part-2-authentication-middleware
Create the middleware:
public class BearerAuthenticationMiddleware
: IFunctionsWorkerMiddleware
{
private readonly ILogger<BearerAuthenticationMiddleware> logger;
public BearerAuthenticationMiddleware(ILogger<BearerAuthenticationMiddleware> logger)
{
this.logger = logger;
}
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
if (context.IsHttpTriggerFunction())
{
var headers = context.BindingContext.BindingData["Headers"]?.ToString();
var httpHeaders = System.Text.Json.JsonSerializer.Deserialize<HttpHeaders>(headers);
if (httpHeaders?.Authorization != null &&
httpHeaders.Authorization.StartsWith("Bearer"))
{
//Validation logic for your token. Here If Bearer present I consider as Valid.
if (httpHeaders.Authorization.Contains("admin"))
{
// Originally based on token get user role.
// Put into context.Items so it will be used by next middleware.
context.Items.Add("UserRole", "Admin");
}
await next(context);
}
else
{
await context.CreateJsonResponse(System.Net.HttpStatusCode.Unauthorized, new { Message = "Token is not valid." });
}
}
else
{
await next(context);
}
}
}
Register the middleware:
public class Program
{
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(configure=>
{
// other middleware also configure following way.
// It will execute in same order it is configured over here.
configure.UseMiddleware<SimpleMiddleware>();
})
.Build();
host.Run();
}
}
Related
I am trying to add in authorization middleware which sets the user in a dependency injected interface. I have tested this and is successful.
I then wanted to add a authorization handler which checks the users role vs the role which is expected, for example to limit certian actions of the api to superusers.
I've created the authorization handler which is here, as you can see I'm dependency injecting the IUserProvider, the authenticated user is set within the middleware of this method.
public class RoleHandler : AuthorizationHandler<RoleRequirement>, IAuthorizationHandler
{
private readonly IUserProvider _userProvider;
public RoleHandler(IUserProvider userProvider)
{
_userProvider = userProvider;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
var currentUser = _userProvider.GetAuthenticatedUser();
if (requirement.Roles.Contains(currentUser.RoleId))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
AuthenticationMiddleware:
public async Task InvokeAsync(HttpContext context, IUserRepository userRepository, IUserProvider userProvider, ApplicationContext applicationContext)
{
var email = context.User.Claims.FirstOrDefault(claim => claim.Type == "preferred_username");
if (email == null)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
var user = await userRepository.FindByEmailAsync(email.Value);
if (user == null)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
userProvider.SetAuthenticatedUser(user);
await _next.Invoke(context);
}
I can see that the AuthorizationHandler is getting called before the AuthorizationMiddleware which is why this is occuring.
However, I've tried to check the context within the HandleRequirementAsync and the user in here is also null.
Here is my user provider, as you can see very basic:
public class UserProvider : IUserProvider
{
private static User AuthenticatedUser { get; set; } = default!;
public User GetAuthenticatedUser()
{
return AuthenticatedUser;
}
public void SetAuthenticatedUser(User user)
{
AuthenticatedUser = user;
}
}
Is there anything I can do to alter the execution order?
EDIT:
Forgot to add in the controller where I am using this:
[Authorize(Policy = "Superuser")]
[Route("{id}"]
public async Task<User> UpdateAsync([FromBody] User user, int id)
{
return await _userService.UpdateAsync(user);
}
And the Program.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Web;
using System.IdentityModel.Tokens.Jwt;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDataAccessLayer();
builder.Services.AddHttpContextAccessor();
builder.Services.AddDbContext<ApplicationContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("Database")).EnableSensitiveDataLogging().EnableDetailedErrors();
});
builder.Services.AddScoped<IAuthorizationHandler, RoleHandler>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Superuser", policy => policy.Requirements.Add(new RoleRequirement(Role.SUPER_USER)));
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<AuthenticationMiddleware>();
app.MapControllers();
app.Run();
I had to solve the opposite problem; the middleware was running before my AuthorizationHandlers.
To get the authorization middleware to run after the authorization attributes have run, create a class that implements the IAuthorizationMiddlewareResultHandler interface as described here.
Additional notes:
For anyone else that wasted an afternoon and stumbles across this answer looking for a way to get the custom authorization middleware to run after your resource-based authorization (after you call the AuthorizeAsync method), you can't. You will either need to use middleware or a result filter.
I want to get Windows logon of the users in GrantResourceOwnerCredentials method of Provider class and validate them. I tried all the possible ways like below, but no luck.
System.Security.Principal.WindowsIdentity.GetCurrent().Name --> It is returning the server name
Request.LogonUserIdentity --> null (It cant be accessed before authentication)
HttpContext.Current.User --> null
From what I understand, if you're only using Windows Authentication, you don't need to worry about GrantResourceOwnerCredentials. Are you trying to use the token authentication as well as the Windows authentication? you should only use Windows Authentication for a Web Api that is going to run on your intranet.
Forgive me if I say things you already know, but from the research I've done, and my thanks go to Dominick Baier on pluralsight, you need to:
Install the Microsoft.Owin and Microsoft.Owin.Security.OAuth nuget packages
Set Windows Authentication to "Enabled" on the Project (F4 properties window) or in the config file
Make sure you have the [Authorize] attribute on your controller and inherit from the ApiController
Specifically implement the Owin middleware (you will need to create three classes and make sure they are configured in the startup.cs class) Have a look at the following code:
1st Middleware class: declare the function
public class ClaimsTransformationOptions
{
public Func<ClaimsPrincipal, Task<ClaimsPrincipal>> ClaimsTransformation { get; set; }
}
2nd Middleware class: this is where the Invoke method is
public class ClaimsTransformationMiddleware
{
readonly ClaimsTransformationOptions _options;
readonly Func<IDictionary<string, object>, Task> _next;
public ClaimsTransformationMiddleware(Func<IDictionary<string, object>, Task> next, ClaimsTransformationOptions options)
{
_next = next;
_options = options;
}
public async Task Invoke(IDictionary<string, object> env)
{
// use Katana OWIN abstractions (optional)
var context = new OwinContext(env);
if (context.Authentication != null &&
context.Authentication.User != null)
{
var transformedPrincipal = await _options.ClaimsTransformation(context.Authentication.User);
context.Authentication.User = new ClaimsPrincipal(transformedPrincipal);
}
await _next(env);
}
}
3rd Middleware class: this is an extension class
public static class ClaimsTransformationMiddlewareExtensions
{
public static IAppBuilder UseClaimsTransformation(this IAppBuilder app,
Func<ClaimsPrincipal, Task<ClaimsPrincipal>> transformation)
{
return app.UseClaimsTransformation(new ClaimsTransformationOptions
{
ClaimsTransformation = transformation
});
}
public static IAppBuilder UseClaimsTransformation(this IAppBuilder app, ClaimsTransformationOptions options)
{
if (options == null)
{
throw new ArgumentNullException("options");
}
app.Use(typeof(ClaimsTransformationMiddleware), options);
return app;
}
}
In the startup class:
public void Configuration(IAppBuilder app)
{
app.UseClaimsTransformation(Transformation);
}
private async Task<ClaimsPrincipal> Transformation(ClaimsPrincipal incoming)
{
if (!incoming.Identity.IsAuthenticated)
{
return incoming;
}
var name = incoming.Identity.Name;
// go to a datastore - find the app specific claims
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, name),
new Claim(ClaimTypes.Role, "foo"),
new Claim(ClaimTypes.Email, "foo#foo.com")
};
var id = new ClaimsIdentity("Windows");
id.AddClaims(claims);
return new ClaimsPrincipal(id);
}
In the Controller (Make sure it has the [Authorize] attribute and inherits from ApiController
public IEnumerable<ViewClaim> Get()
{
var principal = User as ClaimsPrincipal;
return from c in principal.Claims
select new ViewClaim
{
Type = c.Type,
Value = c.Value
};
}
I wanted to add a principle onto the thread by myself , without using the Identity mechanism which really reminds me the old membership/forms authentication mechanics.
So I've managed (successfully) to create a request with principle :
MyAuthMiddleware.cs
public class MyAuthMiddleware
{
private readonly RequestDelegate _next;
public MyAuthMiddleware(RequestDelegate next )
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var claims = new List<Claim>
{
new Claim("userId", "22222222")
};
ClaimsIdentity userIdentity = new ClaimsIdentity(claims ,"MyAuthenticationType");
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
httpContext.User = principal;
await _next(httpContext);
}
}
The Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//app.UseAuthentication(); //removed it. I will set the thread manually
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseMyAuthMiddleware(); // <---------- Myne
app.UseMvc();
app.Run(async context => { await context.Response.WriteAsync("Hello World!"); });
}
And here is the Action in the Controller: (Notice Authorize attribute)
[HttpGet]
[Route("data")]
[Authorize]
public IActionResult GetData()
{
var a=User.Claims.First(f => f.Type == "userId");
return new JsonResult(new List<string> {"a", "b",a.ToString() , User.Identity.AuthenticationType});
}
Ok Let's try calling this method, Please notice that this does work :
So where is the problem 😊?
Please notice that there is an [Authorize] attribute. Now let's remove
setting principle on the thread ( by removing this line ) :
//httpContext.User = principal; // line is remarked
But now when I navigate to :
http://localhost:5330/api/cities/data
I'm being redirected to :
http://localhost:5330/Account/Login?ReturnUrl=%2Fapi%2Fcities%2Fdata
But I'm expecting to see Unauthorized error.
I'm after WebApi alike responses. This is not a website but an API.
Question:
Why don't I see the Unauthorized error ? And how can I make it appear?
Nb here is my ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication( CookieAuthenticationDefaults.AuthenticationScheme )
.AddCookie( CookieAuthenticationDefaults.AuthenticationScheme, a =>
{
a.LoginPath = "";
a.Cookie.Name = "myCookie";
});
services.AddMvc();
}
EDIT
Currently What I've managed to do is to use OnRedirectionToLogin :
But it will be really disappointing if that's the way to go. I'm expecting it to be like webapi.
The default implementation of the OnRedirectToLogin delegate looks like this:
public Func<RedirectContext<CookieAuthenticationOptions>, Task> OnRedirectToLogin { get; set; } = context =>
{
if (IsAjaxRequest(context.Request))
{
context.Response.Headers["Location"] = context.RedirectUri;
context.Response.StatusCode = 401;
}
else
{
context.Response.Redirect(context.RedirectUri);
}
return Task.CompletedTask;
};
As is clear from the code above, the response that gets sent to the client is dependent upon the result of IsAjaxRequest(...), which itself looks like this:
private static bool IsAjaxRequest(HttpRequest request)
{
return string.Equals(request.Query["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal) ||
string.Equals(request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal);
}
This means that the response will be a 401 redirect if either a X-Requested-With request header or query-string value is set to XMLHttpRequest. When you hit your endpoint directly from the browser or from within e.g. Fiddler, this value is not set and so the response is a 302, as observed. Otherwise, when using XHR or Fetch in the browser, this value gets set for you as a header and so a 401 is returned.
I am developing a Web API that in some cases will respond with 500 (ugly design, I know, but can't do anything about it). In tests there's an ApiFixture that contains AspNetCore.TestHost:
public class ApiFixture
{
public TestServer ApiServer { get; }
public HttpClient HttpClient { get; }
public ApiFixture()
{
var config = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
var path = Assembly.GetAssembly(typeof(ApiFixture)).Location;
var hostBuilder = new WebHostBuilder()
.UseContentRoot(Path.GetDirectoryName(path))
.UseConfiguration(config)
.UseStartup<Startup>();
ApiServer = new TestServer(hostBuilder);
HttpClient = ApiServer.CreateClient();
}
}
When I am calling API endpoint with HttpClient from this fixture it should respond with 500, instead I am getting exception that is being raised in the tested controller. I know that in tests it might be a nice feature, but I don't want that - I want to test actual behavior of controller, which is returning internal server error. Is there a way to reconfigure TestServer to return response?
Code in controller action is irrelevant, can be throw new Exception();
You can create an exception handling middleware and use it in tests or better always
public class ExceptionMiddleware
{
private readonly RequestDelegate next;
public ExceptionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
try
{
await this.next(httpContext);
}
catch (Exception ex)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Plain;
httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await httpContext.Response.WriteAsync("Internal server error!");
}
}
}
Now you can register this middleware in your Startup.cs:
...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<ExceptionMiddleware>();
...
app.UseMvc();
}
And if you don't want to use it all the time you can create TestStartup - a child class of your Startup and override Configure method to call UseMiddleware only there. Then you will need to use the new TestStartup class in the tests only.
I have developed a custom middleware in ASP.NET Core but it gets triggered with every request. My intent is to use it only when a request is authorized.
Update:
I create more sample for you and edit my answer. as you see, before the Next() method, I checked every token request. If didn't have Authorization tag in header of context request, be next(), If did check the token.
Now, may you have a question that what is the await _next(context); It is very complex and long, I want to suggest you that visit this link to know what is this issue.
For Create a Middlware you have to control and develop.
Create Generally class as Middleware that work General action like you action Authorization.
Create a static Extension class for relation between Middleware and startup.
And finally Register in startup configuration.
Now this is good sample for you:
General Middlware:
public class RequestTokenMiddleware
{
private readonly RequestDelegate _next;
private readonly SignInManager<User> _signInManager;
public RequestTokenMiddleware(RequestDelegate next, SignInManager<User> signInManager)
{
_next = next;
_signInManager = signInManager;
}
public async Task Invoke(HttpContext context)
{
try
{
var hasAuthorization = context.Request.Headers.ContainsKey("Authorization");
if (!hasAuthorization)
{
await _next(context);
}
else
{
var shouldBeNext = false;
foreach (var item in context.Request.Headers)
{
if (item.Key == "Authorization")
{
using (var contextBudget = BudgetUnitOfWork.Get())
{
var tokenCode = item.Value.ToString().Remove(0, 7);
var token = await contextBudget.Db.Token.FirstOrDefaultAsync(x => x.TokenCode == tokenCode).ConfigureAwait(false);
if (token == null || !token.IsValid)
{
signOut(context);
}
else
{
shouldBeNext = true;
}
}
}
}
if (shouldBeNext)
{
await _next(context);
}
}
}
catch (Exception exc)
{
signOut(context);
}
}
private async void signOut(HttpContext context)
{
try
{
await context.Response.WriteAsync(JsonConvert.SerializeObject(ResultModel.Failure(null, ResultModel.StatusType.InvalidToken)));
}
catch (Exception)
{
throw new Exception();
}
}
}
This is Static Extension class for Relation:
public static class ReuqestTokenMiddlewareExctention
{
public static IApplicationBuilder UseTokenValidator(this IApplicationBuilder applicationBuilder)
{
return applicationBuilder.UseMiddleware<RequestTokenMiddleware>();
}
}
Now Register your Middleware in startup:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<SetiaSettings> options)
{
app.UseTokenValidator();
}
I believe you have a misunderstanding of middleware. Fundamentally middleware is software that will be assembled into the application pipeline to manage each request and response.
That means that middleware is executed every time.
An important concept though, would be how middleware handles each component. In essence top down execution, all allowed code will execute unless conditionally stated otherwise.
Chooses whether to pass the request to the next component in the pipeline.
Can perform work before and after the next component in the pipeline is invokved.
So your early statement is the behavior you should experience, but now you have to ensure the request being instantiated is allowed to execute or not. Your approach for security would basically be in your middleware, allowing that authorization to occur.
An example would be the authorize attribute, but I am not sure what authorization / authentication mechanism you are using.
To explicitly answer your question, you could do something along these lines:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
});
}