WsFederation authentication in Service Fabric Owin Pipeline not working - c#

G'day!
I haven't seen much on this because its all very new at the time of this writing. I am trying to write a service fabric application that serves a web app (html/js) after the user has been authenticated via ACS. I can easily get this to work with OWIN in a non service fabric environment, i.e. a traditional Asp Net application behind IIS. I'm trying to use token authentication with Azure Access Control.
So something to do with the fact that I am now using service fabric has changed the way OWIN works? Below is my OWIN ConfigureApp() function within my Startup.cs in my service fabric application:
public static void ConfigureApp(IAppBuilder appBuilder)
{
appBuilder.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions());
appBuilder.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = _realm,
MetadataAddress = _acsXmlMetaDataUrl
});
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
Notice how I inject the WsFederation middleware before the web api middleware that will eventually be used to serve my browser html/js application. Now when this launches and I do a sanity test like navigating to a REST url my content is served instantly rather than being redirected to Azure Access Control to sign in and get an auth token. In my traditional Asp Net application with the same OWIN configuration I am indeed redirected to Azure Access Control before any resources are served.
So my question is how do I inject WsFed middleware into the OWIN pipeline such that this will work in a service fabric context?
Any assistance would be much appreciated, thank you for your time!

I don't know why this code is working for MVC and not for Service Fabric. I had the same issue, but I found a way to make it work for SF.
This article gives a tutorial.
Basically, in your code, you're not telling it to authenticate. You're setting everything up but you're not starting it.
app.Map("/login", map =>
{
map.Run(async ctx =>
{
if (ctx.Authentication.User == null ||
!ctx.Authentication.User.Identity.IsAuthenticated)
{
ctx.Response.StatusCode = 401;
}
else
{
ctx.Response.Redirect("/");
}
});
});
app.Run(async ctx =>
{
var user = ctx.Authentication.User;
var response = ctx.Response;
response.ContentType = "text/html";
if (user != null && user.Identity.IsAuthenticated)
{
await response.WriteAsync(string.Format("<h2>{0}</h2>",
user.Claims.First().Issuer));
await response.WriteAsync("<dl>");
foreach (var claim in user.Claims)
{
await response.WriteAsync(string.Format(
"<dt>{0}</dt> <dd>{1}</dd>",
claim.Type,
claim.Value));
}
await response.WriteAsync("</dl>");
}
else
{
await ctx.Response.WriteAsync("<h2>anonymous</h2>");
}
});
When you're accessing a link on your website, the code in app.Run starts executing to check if you're logged in. If you're not, in this case, it will write "anonymous" on the page instead of loading your content. To authenticate, go to yourwesite/login and it will redirect you to whatever auth provider you have in the configuration.
Conclusion: add the login, logout and app.Run snippets, give it a final tweak if you have to and that should be it.

Related

How to serve SPA on root using Azure AD's AuthorizeforScopes in a BFF

I am pulling my hair out on this issue and I wonder if anyone here could help me out. If not, does anyone know who can?
My situation is that I have a BFF (Backend-for-frontend) that serves my Angular SPA. I use Azure AD Auth.
I use .NET Core 5 and the up to date Azure AD nuget libs
The BFF is useful because it prevents my Auth tokens from being saved in the browser; the BFF saves it in a http-only secure cookie so the SPA has no knowledge of the user; it has to ask the API about the user.
The BFF also ensures that you can only access the SPA if you are logged in
The BFF also reverse proxies calls to /api to the actual API; which is hosted inside our k8s cluster so you can't reach it from the internet
The diagram basically looks like this. See image.
My problem is that I need to use AuthorizeForScopes for Azure AD Auth but this requires stupid workarounds. As far as I can find online I must put this on a Controller/Action like so:
[HttpGet]
[Authorize]
[AuthorizeForScopes(ScopeKeySection = "Scopes")]
public IActionResult Index()
{
return PhysicalFile($"{_webHostEnvironment.ContentRootPath}/ClientApp/dist/index.html", "text/html");
}
This means that if you go to /, you will hit this endpoint and get served the index.html. This isn't a super neat setup because I would rather just let the UseSpa() in Startup.cs handle this. But I believe this is necessary because I can't just use AuthorizeForScopes in my middleware.
Another problem is that this doesn't work during development because there the UseProxyToSpaDevelopmentServer() in Startup.cs handles all this. If you run this during development you get an error cuz it can't find the index.html
The current solution I have on prod is that the code I posted above instead redirects to /home which the SPA handles, so that way if you go to / you get redirected to /home and the BFF pipeline then redirects you to the SPA and boom, it all works. But this means I can't run my SPA on /
I currently have the following Startup.cs setup for my pipeline. I removed unnecessary code.
app.UseStaticFiles();
app.UseStatusCodePagesWithReExecute("/errorstatus/{0}");
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
// Proxy calls to the API through the BFF so the API can only be reached from within the cluster. This is more secure
app.Map("/api", true, config => RunApiProxy(/* Stuff here */));
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapHealthChecks("/health");
});
if (!Environment.IsDevelopment())
{
// During development we serve files from the dev-server, not from the location of the spa static files
app.UseSpaStaticFiles();
}
// Redirect the user to authenticate if the user isnt at this moment
// All static frontend related files are served using the UseSpaStaticFiles middleware
// What's left is the index.html
app.Use(async (context, next) =>
{
if (context.User?.Identity?.IsAuthenticated != true)
{
await context.ChallengeAsync(WellKnownAuthenticationSchemes.OpenIdConnect);
}
else
{
await next();
}
});
if (Environment.IsDevelopment())
{
// Forward devserver socket calls during development
app.MapWhen(p => p.Request.Path.StartsWithSegments("/sockjs-node"), config =>
{
config.UseSpa(spa =>
{
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
});
});
}
// Serve the angular app
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (Environment.IsDevelopment())
{
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
}
});
How do I implement AuthorizeForScopes without that Action method posted above so I can serve my SPA on / only if the user is [Authorized] and [AuthorizeForScopes]?
How do I implement AuthorizeForScopes without that Action method posted above so I can serve my SPA on / only if the user is [Authorized] and [AuthorizeForScopes]
There are two ways of doing this in the startup.cs > ConfigureServices(IServiceCollection services) method.
Option 1:
services.AddControllers(o =>
{
o.Filters.Add(new RequiredScopeAttribute(new[] { "access_as_user" }));
});
This will automatically add the Scope attribute to all controllers, but this will not work if you have endpoints with [AllowAnonymous].
To get around this the other way is to add your own authorization policy in the same ConfigureServices method for scope:
services.AddAuthorization(o =>
{
var authPolicy = new AuthorizationPolicyBuilder(new[] { JwtBearerDefaults.AuthenticationScheme })
.RequireAuthenticatedUser()
.RequireAssertion(ctx =>
{
// Attempt with Scp claim
Claim scopeClaim = ctx.User.FindFirst(ClaimConstants.Scp);
// Fallback to Scope claim name
if (scopeClaim == null)
{
scopeClaim = ctx.User.FindFirst(ClaimConstants.Scope);
}
return scopeClaim != null && scopeClaim.Value.Split(' ').Intersect(new[] { "access_as_user" }).Any();
})
.Build();
o.DefaultPolicy = authPolicy;
});
The second option sets this auth policy as default for all routes via o.DefaultPolcy = authPolcy but unlike the first option this can be overridden by using [AllowAnonymous] on your method/controller.
You can also use o.AddPolicy("authMeUpScotty", authPolicy) instead of o.DefaultPolicy and use [Authorize(Policy = "authMeUpScotty")] if it was only for particular endpoints.
Hope this helps!

Web API slow performance

I am working on Web API project under .NET Framework 4.6 currently.
It uses bearer token authentication.
But I have noticed the issue with response time of controllers' actions. The response time is quite big even Web API is hosted on local IIS Express. Namely the logging (based on IActionFilter) shows the execution time of the controller is 20 milliseconds, meanwhile Postman shows the response time is about 3 or 4 seconds.
What can be the reason of such difference?
Two steps were taken:
to use the extension method SuppressDefaultHostAuthentication in order to avoid possible side effect from a default authentication. No improvements unfortunately.
to add the dependency injection the default implementation of interfaces which were missing initially and respective exceptions were thrown on Web API start. Namely I have added
.RegisterType<IHttpControllerSelector, DefaultHttpControllerSelector>()
.RegisterType<IHttpActionSelector, ApiControllerActionSelector>(). No improvements unfortunately.
Please find below the content of WebApiConfig.cs and Startup.cs files
WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.MapHttpAttributeRoutes();
//config.SuppressDefaultHostAuthentication();
// TODO: check the necessity to use Storages here, why not on services level
var container = new UnityContainer();
/*some dependecies mapping here*/
container.AddExtension(new Diagnostic());
config.DependencyResolver = new UnityResolver(container);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Filters.Add(new ApiAuthenticationFilter(container.Resolve<BLI.IUserSessionManagement>()));
config.Filters.Add(new ApiAuthorizationFilter(container.Resolve<BLI.IAuthorizer>(), container.Resolve<BET.IAuthLogger>()));
config.Filters.Add(new LoggingFilterAttribute(new BET.ControllerTracer()));
}
Startup.cs file
public void Configuration(IAppBuilder app)
{
//TODO : try to find better solution
BackEnd.WebAPI.Models.UnityResolver ur = (BackEnd.WebAPI.Models.UnityResolver)System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver;
Type providerType = Type.GetType("Microsoft.Owin.Security.OAuth.IOAuthAuthorizationServerProvider, Microsoft.Owin.Security.OAuth", true);
ApiOAuthAuthorizationServerProvider serverProvider = (ApiOAuthAuthorizationServerProvider)ur.GetService(providerType);
//
OAuthAuthorizationServerOptions oAuthOptions = new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new PathString("/auth"),
Provider = serverProvider,
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
AllowInsecureHttp = true
};
app.UseOAuthAuthorizationServer(oAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
additionally taken actions:
disable authentication and authorization filters. No improvement detected.
perform the same tests on Azure. The same situation: logging based on action filter reports high performance of contoller actions, but client receives responses with essential delay as on local IIS Express
The reason was found.
It was due to too complex controller constructor.
The revision has solved the issue

Implement two authentication options (Token and Certificate) in ASP Net Core

[Target netcoreapp3.1]
Hi there! So I have this Web Api that is protected by a middleware of this form in my Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
//other services configuration
services.AddProtectedWebApi(options => { /* config */};
//other services configuration
}
This verifies Jwt Tokens issued by Azure and grants access to the API; it works fine.
At present, I have a front-end angular client website where a user signs in via Azure AD. Angular sends the token to my web API and everything works.
I would now like to use the same webapp to handle query requests from a user without credentials, but with a client certificate that would have been provided in advance. So basically, I'd like to authenticate on my Angular WebSite via Azure OR via a client cert. Angular would then follow up the information to my webapp, which would in turn authenticate the user with the appropriate method.
To be clear, I still want someone to be able to log in without a certificate by using his Azure account.
Is there a simple way to have two authentication options in this case without having to create a separate webapp? I read a bit there : https://learn.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-3.1#optional-client-certificates
But it seems it'd only work on the preview of ASP.NET Core 5, which I can't use in my situation.
Hope what follows will help someone!
I eventually found this link : https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-3.1
It explains how to implement multiple authorization policies that both have a chance to succeed. Below is the solution I found using IIS after a bit more research:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//other services configuration
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = true;
});
services.Configure<CertificateForwardingOptions>(options =>
{
options.CertificateHeader = {/*your header present in client request*/};
});
//other services configuration
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.AllowedCertificateTypes =/*Whatever you need*/;
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
if ({/*CertValidationClass*/}.ValidateCertificate(context.ClientCertificate))
{
context.Success();
}
else
{
context.Fail("invalid cert");
}
return Task.CompletedTask;
}
};
});
services.AddProtectedWebApi(options => { /* config */};
//other services configuration
}
{CertValidationClass} being a service or helper class custom made to verify all I have to verify to approve the certificate. Obviously you can add a lot more verifying and actions on your own to this template.
I already had app.UseAuthentication(); app.UseAuthorization(); in my middleware pipeline, no need to change that, but you do have to add app.UseCertificateForwarding(); before these two.
Now I just had to specify above the controller I wanted to protect that I wanted to use both Authorization methods, and just like that, if one fails, it falls back on the other and it works perfectly, I tested by making requests via Insomnia with/without tokens and with/without certficates.
MyApiController.cs
[Authorize(AuthenticationSchemes = AuthSchemes)]
public class MyApiController
{
//Just add the schemes you want used here
private const string AuthSchemes =
CertificateAuthenticationDefaults.AuthenticationScheme; + "," +
JwtBearerDefaults.AuthenticationScheme;

ASP.NET Core Identify Auth + Angular: Login Not Working

I'm presently developing an application with an ASP.NET Core (2.1) API server/backend and an Angular Single Page Application frontend.
For user authentication, I'm using ASP.NET Core Identity, along with several of the default methods provided within AccountController for login, logout, register, forgot password, etc. My Angular application accesses this API to perform these actions.
The Angular client files are hosted directly out of wwwroot in the ASP.NET Core application. I am hosting using IIS Express (I have tried IIS as well to no avail) under localhost:5000.
Authentication is configured in ASP.NET Core's Startup class with the following:
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = false;
config.Password.RequiredLength = 8;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
// Prevent API from redirecting to login or Access Denied screens (Angular handles these).
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = 403;
return Task.CompletedTask;
};
});
Register and Forgot Password both work without a hitch. The API is called and the appropriate actions are taken.
Login appears to work: The method is invoked within Angular:
#Injectable()
export class AuthService extends BaseService {
constructor(private httpClient: HttpClient) {
super(httpClient, `${config.SERVER_API_URL}/account`);
}
// Snip
login(login: Login): Observable<boolean> {
return this.httpClient.post<boolean>(`${this.actionUrl}/Login`, login)
.catch(this.handleError);
}
// Snip
}
The ASP.NET Core API gets the call, logs the user in, and returns success to indicate to Angular that login worked and they should proceed to redirect the user:
namespace ApartmentBonds.Web.Api.Account
{
[Route("api/[controller]/[action]")]
public class AccountController : APIControllerBase
{
// Snip
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] LoginViewModel model, string returnUrl = null)
{
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return Ok(true);
}
}
return Ok(false);
}
// Snip
}
}
The session cookie is created successfully, and I can see it within the browser (using Firefox Quantum in this screenshot, but have also tried in IE, in Firefox incognito, etc):
To test, I have another API method which simply returns whether or not the calling user is signed in:
// Angular
isLoggedIn(): Observable<boolean> {
return this.httpClient.get<boolean>(`${this.actionUrl}/IsLoggedIn`)
.catch(this.handleError);
}
// ASP.NET Core API
[HttpGet]
[AllowAnonymous]
public IActionResult IsLoggedIn()
{
return Ok(_signInManager.IsSignedIn(User));
}
I make this call within the browser, and it returns false. (All other aspects of the site that depend on the user being logged in also show the user is not logged in -- this method is just a quick and dirty way to verify this problem.) Note that the client is correctly sending the Identity cookie along with the request:
Since the only site in question here is localhost:5000 I'm not sure why this is not working.
Things I've tried
Within services.ConfigureApplicationCookie
Setting options.Events.Cookie.Domain to localhost:5000 explicitly
Setting options.Events.Cookie.Path to /api
Clearing all cookies and trying incognito/other browsers (to rule out other localhost site cookies potentially clobbering the request)
Hosting the site via IIS (localhost:8888) after Publishing the Core Application
Anyone have any ideas?
In order for authentication handlers to run on the request, you need app.UseAuthentication(); in the middleware pipeline (before middleware that requires it like MVC).
This is a longshot, but maybe the token needs: "Bearer " (with the space) in front of the actual token.
I think you shouldn't use User property from controller when you are using AllowAnonymousAttribute.
Try to do request to some authorize endpoint. If you are not sign in, then you will get status code 401(Unathorized).

Require Authentication for all requests to an OWIN application

I am working with a self-hosted OWIN application and am trying to figure out how to require authentication/authorization for all requests (or arbitrary requests).
Some of the individual components in the pipeline have their own Authorization facilities (ex. WebAPI, SignalR, Nancy) but that seems somewhat redundant when I want to restrict everything. Additionally, some middle-ware does not have authorization support (ex. Microsoft.Owin.StaticFiles).
If my OWIN Startup looks something like this:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.RequireSsl();
app.UseCookieAuthentication(new CookieAuthenticationOptions());
//...
app.UseGoogleAuthentication();
// ** Need to add something that restricts access **
app.UseDirectoryBrowser();
}
}
How do I require the user have authenticated (redirecting if necessary) before serving the directory browser? (The directory browser could arbitrarily be other OWIN components.)
Put this between your auth middleware and the components you want to protect. It will check to ensure that each request is authenticated.
app.Use(async (context, next) =>
{
var user = context.Authentication.User;
if (user == null || user.Identity == null || !user.Identity.IsAuthenticated)
{
context.Authentication.Challenge();
return;
}
await next();
});

Categories

Resources