Authorization for anonymous user (automatic authentication) - c#

UPDATE: Unfortunately, a Windows reboot solved this issue -.-
In our ASP.NET Core (1.0 RC2) application, we have the following requirement: only users from the internal network should be able to access some "Debug" pages (hosted by MVC Core). It's a public website and we don't have user logins, instead we managed it until now with a custom IP-address based authorization (note: this is not a security risk in our case, because we have a proxy in between, so the IP address cannot be spoofed from outside).
We want to implement such an IP-address based authorization in ASP.NET Core, as well. We use a custom policy "DebugPages" for this and corresponding [Authorize(Policy="DebugPages")] definitions on the MVC controller. Then we noticed, that we must have an authenticated user to get the AuthorizeAttribute to jump in and we create one in the request pipeline, which yields to the following code in Startup.cs (shortened for brevity):
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthorization(options =>
{
options.AddPolicy(
"DebugPages",
policy => policy.RequireAssertion(
async context => await MyIPAuthorization.IsAuthorizedAsync()));
});
}
public void Configure(IApplicationBuilder app)
{
...
app.Use(async (context, next) =>
{
context.User = new ClaimsPrincipal(new GenericIdentity("anonymous"));
await next.Invoke();
});
...
}
Now this works fine when run in Debug by Visual Studio 2015 (with IIS Express).
But unfortunately it doesn't work when run directly by dotnet run (with Kestrel) from the command line. In this case we get the following exception:
InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
The same error occurs when we provide the current Windows principal instead of the principal with a custom anonymous identity -- so everytime when the user is automatic-ally authenticated...
So, why is there a difference between hosting in IIS Express and Kestrel? Any suggestions how to solve the issue?

So, after some research, as I mentioned in the comments, I have found that httpContext.Authentication.HttpAuthhenticationFeature.Handler is null, when you starts the application under the "selfhosted" kestrel. But when you use IIS the Handler has instantiated by Microsoft.AspNetCore.Server.IISIntegration.AuthenticationHandler. This specific handler implementation is part of the .UseIISIntegration() in Program.cs.
So, I've decided to use a part of this implementation in my App and handle nonauthenticated requests.
For my WebAPI (without any Views) service I use IdentityServer4.AccessTokenValidation that uses behind the scenes OAuth2IntrospectionAuthentication and JwtBearerAuthentication.
Create files
KestrelAuthenticationMiddleware.cs
public class KestrelAuthenticationMiddleware
{
private readonly RequestDelegate _next;
public KestrelAuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var existingPrincipal = context.Features.Get<IHttpAuthenticationFeature>()?.User;
var handler = new KestrelAuthHandler(context, existingPrincipal);
AttachAuthenticationHandler(handler);
try
{
await _next(context);
}
finally
{
DetachAuthenticationhandler(handler);
}
}
private void AttachAuthenticationHandler(KestrelAuthHandler handler)
{
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
if (auth == null)
{
auth = new HttpAuthenticationFeature();
handler.HttpContext.Features.Set(auth);
}
handler.PriorHandler = auth.Handler;
auth.Handler = handler;
}
private void DetachAuthenticationhandler(KestrelAuthHandler handler)
{
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
if (auth != null)
{
auth.Handler = handler.PriorHandler;
}
}
}
KestrelAuthHandler.cs
internal class KestrelAuthHandler : IAuthenticationHandler
{
internal KestrelAuthHandler(HttpContext httpContext, ClaimsPrincipal user)
{
HttpContext = httpContext;
User = user;
}
internal HttpContext HttpContext { get; }
internal ClaimsPrincipal User { get; }
internal IAuthenticationHandler PriorHandler { get; set; }
public Task AuthenticateAsync(AuthenticateContext context)
{
if (User != null)
{
context.Authenticated(User, properties: null, description: null);
}
else
{
context.NotAuthenticated();
}
if (PriorHandler != null)
{
return PriorHandler.AuthenticateAsync(context);
}
return Task.FromResult(0);
}
public Task ChallengeAsync(ChallengeContext context)
{
bool handled = false;
switch (context.Behavior)
{
case ChallengeBehavior.Automatic:
// If there is a principal already, invoke the forbidden code path
if (User == null)
{
goto case ChallengeBehavior.Unauthorized;
}
else
{
goto case ChallengeBehavior.Forbidden;
}
case ChallengeBehavior.Unauthorized:
HttpContext.Response.StatusCode = 401;
// We would normally set the www-authenticate header here, but IIS does that for us.
break;
case ChallengeBehavior.Forbidden:
HttpContext.Response.StatusCode = 403;
handled = true; // No other handlers need to consider this challenge.
break;
}
context.Accept();
if (!handled && PriorHandler != null)
{
return PriorHandler.ChallengeAsync(context);
}
return Task.FromResult(0);
}
public void GetDescriptions(DescribeSchemesContext context)
{
if (PriorHandler != null)
{
PriorHandler.GetDescriptions(context);
}
}
public Task SignInAsync(SignInContext context)
{
// Not supported, fall through
if (PriorHandler != null)
{
return PriorHandler.SignInAsync(context);
}
return Task.FromResult(0);
}
public Task SignOutAsync(SignOutContext context)
{
// Not supported, fall through
if (PriorHandler != null)
{
return PriorHandler.SignOutAsync(context);
}
return Task.FromResult(0);
}
}
And in the Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMiddleware<KestrelAuthenticationMiddleware>();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = Configuration[AppConstants.Authority],
RequireHttpsMetadata = false,
AutomaticChallenge = true,
ScopeName = Configuration[AppConstants.ScopeName],
ScopeSecret = Configuration[AppConstants.ScopeSecret],
AutomaticAuthenticate = true
});
app.UseMvc();
}

Related

why Authorization Handler invoked two time when added a global authorization filter?

I used policy-based authorization in asp.net core mvc 3.1, I have an authorization requirement for read, named ReadAuthorizationRequirement, also I have a global IPAllowedAuthorizationRequirement that checks a user's IP and if this IP is allowed succeed the context.
the problem is when I add global authorization filter read requirement invoked two times in first invoke its resource is Microsoft.AspNetCore.Routing.RouteEndpoint in second invoke its resource is Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.AuthorizationFilterContextSealed
and this prevent context to authorize successfully.
My Startup.cs is as below:
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
services.AddControllersWithViews(options =>
{
options.Filters.Add(new AuthorizeFilter(policy: Constants.Authorization.IpAllowed));
options.Filters.Add(typeof(Filters.RequestLoggingAttribute));
});
ReadAuthorizationHandler.cs
public class ReadAuthorizationHandler : AuthorizationHandler<ReadAuthorizationRequirement>
{
private readonly IApplicationRouteService _applicationRouteService;
public ReadAuthorizationHandler(IApplicationRouteService applicationRouteService)
{
_applicationRouteService = applicationRouteService;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ReadAuthorizationRequirement requirement)
{
var roles = AuthorizationHanderHelper.FindRole(_applicationRouteService, context, requirement);
if (roles.Any(role => role.Read == true))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
IpAllowedAuthorizationHandler.cs
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IpAllowedAuthorizationRequirement requirement)
{
if (context.Resource is Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext mvcContext)
{
var userIps = ((ClaimsIdentity)context.User.Identity).Claims
.Where(c => c.Type == Constants.Authorization.ClaimTypes.IpAddress)
.Select(c => c.Value).ToList();
var currentIp = mvcContext.HttpContext.Connection.RemoteIpAddress;
var bytes = currentIp.GetAddressBytes();
var badIp = true;
foreach (var address in userIps)
{
if (IPAddress.TryParse(address, out IPAddress testIp))
{
if (testIp.GetAddressBytes().SequenceEqual(bytes))
{
badIp = false;
break;
}
}
}
if (!badIp)
{
context.Succeed(requirement);
}
else
{
var routeData = mvcContext.RouteData;
var action = routeData.Values["action"] as string;
var controller = routeData.Values["controller"] as string;
var area = routeData.DataTokens["area"] as string;
if (string.IsNullOrEmpty(area) && controller == "Account" && action == "AccessDenied")
{
mvcContext.ModelState.TryAddModelError("", $"IP izni verilmemiştir! (IP: {currentIp})");
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
}
return Task.CompletedTask;
}
This is pictures of calls to read on IpAllowed handlers:
1- first call is happened in Read authorization handler and context is endpoint (this call is succeed):
2- second call is happened in Read authorization handler and context is Mvc's AuthorizationFilterContext and this time it fails. in second call IpAllowed is in pending requirements.
As mentioned here
Setting Global Authorization policies using defaultpolicy and the fallback policy in aspnet core 3
and here
I used old style global authorization filter for asp.net mvc core, that was ok with asp.net core 2.2, but in 3.0+ its not working as expected when using endpoint routing.
so I changed my code as below:
Start.cs:
removed fallback policy from configure services, because I added IpAllowed policy as global requirement as below.
removed global authorization filter from options in configure services method and added Require authorization inside configure method inside app.useEndpoints as below:
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute().RequireAuthorization(Constants.Authorization.IpAllowed);
});
IpAllowedAuthorizationHandler.cs
updated handler as below:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IpAllowedAuthorizationRequirement requirement)
{
if (context.Resource is Microsoft.AspNetCore.Routing.RouteEndpoint endpoint)
{
var userIps = ((ClaimsIdentity)context.User.Identity).Claims
.Where(c => c.Type == Constants.Authorization.ClaimTypes.IpAddress)
.Select(c => c.Value).ToList();
var currentIp = _contextAccessor?.HttpContext.Connection.RemoteIpAddress;
if(currentIp == null)
{
context.Fail();
return Task.CompletedTask;
}
var bytes = currentIp.GetAddressBytes();
var badIp = true;
foreach (var address in userIps)
{
if (IPAddress.TryParse(address, out IPAddress testIp))
{
if (testIp.GetAddressBytes().SequenceEqual(bytes))
{
badIp = false;
break;
}
}
}
if (!badIp)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
return Task.CompletedTask;
}

Checking ABP permissions from claims

I am using ABP version 3.9 for ASP.NET Core. We have an existing Identity Server 4 instance that provides role information in the form of claims (via OIDC). I would like to hook into ABP's permissions system for dynamic menu-ing, among other things. Since I'm not using a local Identity Server1 implementation, I don't see a way to convert the claims to permissions.
My thought is to use custom middleware to handle it like this:
public class ClaimsToAbpPermissionsMiddleware
{
private readonly RequestDelegate _next;
public ClaimsToAbpPermissionsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Get user
ClaimsPrincipal user = context.User;
// foreach claim of type "role"
var roleClaims = user.Claims.Where(claim => claim.Type == "role");
foreach (Claim claim in roleClaims)
{
switch (claim.Value)
{
case "TestResults":
// Assign applicable permission
// ...
break;
default:
break;
}
}
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}
The problem is that I don't know how to apply the permissions. User Management2 suggests using UserManager, which appears to have the necessary functionality.
First, Abp.ZeroCore doesn't have this class. Can I use Abp.Zero in an ASP.NET Core app?
Second, it looks like Abp.Authorization.Users.AbpUserManager has similar functionality. Can I use this? However, I'm not sure how to inject this into Startup, as it requires some types for injecting AbpUserManager<TRole, TUser> and I'm not clear on what types to use.
Any help would be appreciated.
Additionally, is what I'm trying feasible? Is there a better approach?
The issue is that I'm not using Module Zero's User Management2. All of our users are in a separate ASP.NET Identity (Identity Server) implementation, and I'm using the template from ABP that doesn't include the models/pages for user/tenant/role, so I don't have the types to inject AbpUserManager<TRole, TUser>.
1https://aspnetboilerplate.com/Pages/Documents/Zero/Identity-Server
2https://aspnetboilerplate.com/Pages/Documents/Zero/User-Management
Update: So I implemented IPermissionChecker as per the answer below:
PermissionChecker (not in love with the switch, but am going to refactor once it's working):
public class PermissionChecker : IPermissionChecker, ITransientDependency
{
private readonly IHttpContextAccessor _httpContextAccessor;
public PermissionChecker(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public Task<bool> IsGrantedAsync(string permissionName)
{
bool isGranted = false;
// Get user
var user = _httpContextAccessor.HttpContext.User;
// Get claims of type "role"
var roleClaims = user.Claims.Where(claim => claim.Type == "role");
// Check for applicable permission based on role permissions
foreach (Claim claim in roleClaims)
{
switch (claim.Value)
{
case "TestResults":
// Assign applicable permission
// ...
if(permissionName.ToLowerInvariant() == "TestResults".ToLowerInvariant())
{
isGranted = true;
}
break;
case "About":
// Assign applicable permission
// ...
if (permissionName.ToLowerInvariant() == "AboutView".ToLowerInvariant())
{
isGranted = true;
}
break;
case "Account":
// Assign applicable permission
// ...
if (permissionName.ToLowerInvariant() == "AccountView".ToLowerInvariant())
{
isGranted = true;
}
break;
default:
break;
}
if (isGranted)
{
break;
}
}
//return new Task<bool>
return Task.FromResult(isGranted);
}
public Task<bool> IsGrantedAsync(UserIdentifier user, string permissionName)
{
return IsGrantedAsync(permissionName);
}
}
AuthorizationProvider:
public class MyAuthProvider : AuthorizationProvider
{
public override void SetPermissions(IPermissionDefinitionContext context)
{
var about = context.CreatePermission("AboutView");
var account = context.CreatePermission("AccountView");
var testResults = context.CreatePermission("TestResults");
}
}
Module Initialization:
public class CentralPortalCoreModule : AbpModule
{
public override void PreInitialize()
{
Configuration.Auditing.IsEnabledForAnonymousUsers = true;
CentralPortalLocalizationConfigurer.Configure(Configuration.Localization);
IocManager.Register<IPermissionChecker, PermissionChecker>(DependencyLifeStyle.Transient);
Configuration.Authorization.Providers.Add<MyAuthProvider>();
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(CentralPortalCoreModule).GetAssembly());
}
}
Navigation Provider:
public override void SetNavigation(INavigationProviderContext context)
{
context.Manager.MainMenu
.AddItem(
new MenuItemDefinition(
PageNames.Home,
L("HomePage"),
url: "",
icon: "fa fa-home"
)
).AddItem(
new MenuItemDefinition(
PageNames.About,
L("About"),
url: "Home/About",
icon: "fa fa-info",
requiredPermissionName: "AboutView",
requiresAuthentication: true
)
).AddItem(
new MenuItemDefinition(
"Results",
L("Results"),
url: "Results",
icon: "fa fa-tasks",
requiredPermissionName: "TestResults"
)
)
.AddItem(
new MenuItemDefinition(
PageNames.Account,
L("Account"),
url: "Account",
icon: "fa fa-info",
requiredPermissionName:"AccountView",
requiresAuthentication: true
)
)
.AddItem(
new MenuItemDefinition(
PageNames.Contact,
L("Contact"),
url: "Contact",
icon: "fa fa-info"
)
);
}
Adding breakpoints shows that the PermissionChecker is initialized as is the AuthProvider. Unfortunately, the navigation doesn't show the protected items even though I am authenticated and have valid role claims. The IsGrantedAsync method is never called. I tried setting the nav items with requiresAuthentication = true and false and neither changed anything.
Am I missing something?
Well, you're referring to Module Zero documentation but not using Module Zero.
If you don't store users, then it may not make sense to store user permissions.
You can implement IPermissionChecker to check permissions from claims.
public class PermissionChecker : IPermissionChecker, ITransientDependency
{
private readonly IHttpContextAccessor _httpContextAccessor;
public PermissionChecker(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<bool> IsGrantedAsync(string permissionName)
{
// Get user
var user = _httpContextAccessor.HttpContext.User;
// Get claims of type "role"
var roleClaims = user.Claims.Where(claim => claim.Type == "role");
// Check for applicable permission based on role permissions
// ...
}
public Task<bool> IsGrantedAsync(UserIdentifier user, string permissionName)
{
return IsGrantedAsync(permissionName);
}
}
Since AuthorizationHelper checks AbpSession.UserId, you'll have to override its method.
public class NonUserAuthorizationHelper : AuthorizationHelper
{
private readonly IAuthorizationConfiguration _authConfiguration
public NonUserAuthorizationHelper(IFeatureChecker featureChecker, IAuthorizationConfiguration authConfiguration)
: base(featureChecker, authConfiguration)
{
_authConfiguration = authConfiguration;
}
public override async Task AuthorizeAsync(IEnumerable<IAbpAuthorizeAttribute> authorizeAttributes)
{
if (!_authConfiguration.IsEnabled)
{
return;
}
// if (!AbpSession.UserId.HasValue)
// {
// throw new AbpAuthorizationException(
// LocalizationManager.GetString(AbpConsts.LocalizationSourceName, "CurrentUserDidNotLoginToTheApplication")
// );
// }
foreach (var authorizeAttribute in authorizeAttributes)
{
await PermissionChecker.AuthorizeAsync(authorizeAttribute.RequireAllPermissions, authorizeAttribute.Permissions);
}
}
}
And then replace it in the PreInitialize method of your *.Core module.
// using Abp.Configuration.Startup;
public override void PreInitialize()
{
Configuration.ReplaceService<IAuthorizationHelper, NonUserAuthorizationHelper>();
}

How to authenticate facebook web api in asp.net core 2

I'm currently building my very first mobile app in Xamarin.Forms. The app has a facebook login and after the user has been logged in I'm storing the facebook token because I want to use it as a bearer-token to authenticate any further requests against an API.
The API is a .NET core 2.0 project and I am struggling to get the authentication working.
In my Xamarin.Forms app the facebook token is set as bearer-token with the following code;
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", UserToken);
As far as I know, this properly sets the bearer token in the headers of the request.
I've talked to a colleague of mine about this and he told me to take a look at Identityserver4 which should support this. But for now, I've decided not to do that since for me, at this moment, it is overhead to implement this. So I've decided to stay with the idea to use the facebook token as bearer token and validate this.
So the next step for me is to find a way to authenticate the incoming bearer token with Facebook to check if it is (still) valid.
So I've configured the startup for my API projects as following;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddFacebook(o =>
{
o.AppId = "MyAppId";
o.AppSecret = "MyAppSecret";
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//Enable authentication
app.UseAuthentication();
//Enable support for default files (eg. default.htm, default.html, index.htm, index.html)
app.UseDefaultFiles();
//Configure support for static files
app.UseStaticFiles();
app.UseMvc();
}
}
But when I'm using postman to do a request and test if everything is working I'm receiving the following error;
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
What am I doing wrong here?
EDIT:
In the mean time if been busy trying to find a solution for this. After reading a lot on Google it seems thatadding an AuthorizationHandler is the way to go at the moment. From there on I can make request to facebook to check if the token is valid. I've added the following code to my ConfigureServices method;
public void ConfigureServices(IServiceCollection services)
{
//Other code
services.AddAuthorization(options =>
{
options.AddPolicy("FacebookAuthentication", policy => policy.Requirements.Add(new FacebookRequirement()));
});
services.AddMvc();
}
And I've created a FacebookRequirement which will help me handling the policy;
public class FacebookRequirement : AuthorizationHandler<FacebookRequirement>, IAuthorizationRequirement
{
private readonly IHttpContextAccessor contextAccessor;
public FacebookRequirement(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FacebookRequirement requirement)
{
//var socialConfig = new SocialConfig{Facebook = new SocialApp{AppId = "MyAppId", AppSecret = "MyAppSecret" } };
//var socialservice = new SocialAuthService(socialConfig);
//var result = await socialservice.VerifyFacebookTokenAsync()
var httpContext = contextAccessor.HttpContext;
if (httpContext != null && httpContext.Request.Headers.ContainsKey("Authorization"))
{
var token = httpContext.Request.Headers.Where(x => x.Key == "Authorization").ToList();
}
context.Succeed(requirement);
return Task.FromResult(0);
}
}
The problem I'm running into now is that I don't know where to get the IHttpContextAccessor. Is this being injected somehow? Am I even on the right path to solve this issue?
I ended up creating my own AuthorizationHandler to validate incoming requests against facebook using bearer tokens. In the future I'll probably start using Identityserver to handle multiple login types. But for now facebook is sufficient.
Below is the solution for future references.
First create an FacebookRequirement class inheriting from AuthorizationHandler;
public class FacebookRequirement : AuthorizationHandler<FacebookRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FacebookRequirement requirement)
{
var socialConfig = new SocialConfig { Facebook = new SocialApp { AppId = "<FacebookAppId>", AppSecret = "<FacebookAppSecret>" } };
var socialservice = new SocialAuthService(socialConfig);
var authorizationFilterContext = context.Resource as AuthorizationFilterContext;
if (authorizationFilterContext == null)
{
context.Fail();
return Task.FromResult(0);
}
var httpContext = authorizationFilterContext.HttpContext;
if (httpContext != null && httpContext.Request.Headers.ContainsKey("Authorization"))
{
var authorizationHeaders = httpContext.Request.Headers.Where(x => x.Key == "Authorization").ToList();
var token = authorizationHeaders.FirstOrDefault(header => header.Key == "Authorization").Value.ToString().Split(' ')[1];
var user = socialservice.VerifyTokenAsync(new ExternalToken { Provider = "Facebook", Token = token }).Result;
if (!user.IsVerified)
{
context.Fail();
return Task.FromResult(0);
}
context.Succeed(requirement);
return Task.FromResult(0);
}
context.Fail();
return Task.FromResult(0);
}
}
Add the following classes which will contain the configuration an represent the user;
public class SocialConfig
{
public SocialApp Facebook { get; set; }
}
public class SocialApp
{
public string AppId { get; set; }
public string AppSecret { get; set; }
}
public class User
{
public Guid Id { get; set; }
public string SocialUserId { get; set; }
public string Email { get; set; }
public bool IsVerified { get; set; }
public string Name { get; set; }
public User()
{
IsVerified = false;
}
}
public class ExternalToken
{
public string Provider { get; set; }
public string Token { get; set; }
}
And last but not least, the SocialAuthService class which will handle the requests with facebook;
public class SocialAuthService
{
private SocialConfig SocialConfig { get; set; }
public SocialAuthService(SocialConfig socialConfig)
{
SocialConfig = socialConfig;
}
public async Task<User> VerifyTokenAsync(ExternalToken exteralToken)
{
switch (exteralToken.Provider)
{
case "Facebook":
return await VerifyFacebookTokenAsync(exteralToken.Token);
default:
return null;
}
}
private async Task<User> VerifyFacebookTokenAsync(string token)
{
var user = new User();
var client = new HttpClient();
var verifyTokenEndPoint = string.Format("https://graph.facebook.com/me?access_token={0}&fields=email,name", token);
var verifyAppEndpoint = string.Format("https://graph.facebook.com/app?access_token={0}", token);
var uri = new Uri(verifyTokenEndPoint);
var response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
dynamic userObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
uri = new Uri(verifyAppEndpoint);
response = await client.GetAsync(uri);
content = await response.Content.ReadAsStringAsync();
dynamic appObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
if (appObj["id"] == SocialConfig.Facebook.AppId)
{
//token is from our App
user.SocialUserId = userObj["id"];
user.Email = userObj["email"];
user.Name = userObj["name"];
user.IsVerified = true;
}
return user;
}
return user;
}
}
This will validate the Facebook token coming from the request as bearer token, with Facebook to check if it's still valid.

ASP.NET Core Web API Authentication

I'm struggling with how to set up authentication in my web service.
The service is build with the ASP.NET Core web api.
All my clients (WPF applications) should use the same credentials to call the web service operations.
After some research, I came up with basic authentication - sending a username and password in the header of the HTTP request.
But after hours of research, it seems to me that basic authentication is not the way to go in ASP.NET Core.
Most of the resources I found are implementing authentication using OAuth or some other middleware. But that seems to be oversized for my scenario, as well as using the Identity part of ASP.NET Core.
So what is the right way to achieve my goal - simple authentication with username and password in a ASP.NET Core web service?
Now, after I was pointed in the right direction, here's my complete solution:
This is the middleware class which is executed on every incoming request and checks if the request has the correct credentials. If no credentials are present or if they are wrong, the service responds with a 401 Unauthorized error immediately.
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next;
public AuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic"))
{
//Extract credentials
string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
int seperatorIndex = usernamePassword.IndexOf(':');
var username = usernamePassword.Substring(0, seperatorIndex);
var password = usernamePassword.Substring(seperatorIndex + 1);
if(username == "test" && password == "test" )
{
await _next.Invoke(context);
}
else
{
context.Response.StatusCode = 401; //Unauthorized
return;
}
}
else
{
// no authorization header
context.Response.StatusCode = 401; //Unauthorized
return;
}
}
}
The middleware extension needs to be called in the Configure method of the service Startup class
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMiddleware<AuthenticationMiddleware>();
app.UseMvc();
}
And that's all! :)
A very good resource for middleware in .Net Core and authentication can be found here:
https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/
You can implement a middleware which handles Basic authentication.
public async Task Invoke(HttpContext context)
{
var authHeader = context.Request.Headers.Get("Authorization");
if (authHeader != null && authHeader.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
{
var token = authHeader.Substring("Basic ".Length).Trim();
System.Console.WriteLine(token);
var credentialstring = Encoding.UTF8.GetString(Convert.FromBase64String(token));
var credentials = credentialstring.Split(':');
if(credentials[0] == "admin" && credentials[1] == "admin")
{
var claims = new[] { new Claim("name", credentials[0]), new Claim(ClaimTypes.Role, "Admin") };
var identity = new ClaimsIdentity(claims, "Basic");
context.User = new ClaimsPrincipal(identity);
}
}
else
{
context.Response.StatusCode = 401;
context.Response.Headers.Set("WWW-Authenticate", "Basic realm=\"dotnetthoughts.net\"");
}
await _next(context);
}
This code is written in a beta version of asp.net core. Hope it helps.
To use this only for specific controllers for example use this:
app.UseWhen(x => (x.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase)),
builder =>
{
builder.UseMiddleware<AuthenticationMiddleware>();
});
I think you can go with JWT (Json Web Tokens).
First you need to install the package System.IdentityModel.Tokens.Jwt:
$ dotnet add package System.IdentityModel.Tokens.Jwt
You will need to add a controller for token generation and authentication like this one:
public class TokenController : Controller
{
[Route("/token")]
[HttpPost]
public IActionResult Create(string username, string password)
{
if (IsValidUserAndPasswordCombination(username, password))
return new ObjectResult(GenerateToken(username));
return BadRequest();
}
private bool IsValidUserAndPasswordCombination(string username, string password)
{
return !string.IsNullOrEmpty(username) && username == password;
}
private string GenerateToken(string username)
{
var claims = new Claim[]
{
new Claim(ClaimTypes.Name, username),
new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds().ToString()),
};
var token = new JwtSecurityToken(
new JwtHeader(new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Secret Key You Devise")),
SecurityAlgorithms.HmacSha256)),
new JwtPayload(claims));
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
After that update Startup.cs class to look like below:
namespace WebAPISecurity
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = "JwtBearer";
options.DefaultChallengeScheme = "JwtBearer";
})
.AddJwtBearer("JwtBearer", jwtBearerOptions =>
{
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Secret Key You Devise")),
ValidateIssuer = false,
//ValidIssuer = "The name of the issuer",
ValidateAudience = false,
//ValidAudience = "The name of the audience",
ValidateLifetime = true, //validate the expiration and not before values in the token
ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
}
And that's it, what is left now is to put [Authorize] attribute on the Controllers or Actions you want.
Here is a link of a complete straight forward tutorial.
http://www.blinkingcaret.com/2017/09/06/secure-web-api-in-asp-net-core/
I have implemented BasicAuthenticationHandler for basic authentication so you can use it with standart attributes Authorize and AllowAnonymous.
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
{
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authHeader = (string)this.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
{
//Extract credentials
string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
int seperatorIndex = usernamePassword.IndexOf(':', StringComparison.OrdinalIgnoreCase);
var username = usernamePassword.Substring(0, seperatorIndex);
var password = usernamePassword.Substring(seperatorIndex + 1);
//you also can use this.Context.Authentication here
if (username == "test" && password == "test")
{
var user = new GenericPrincipal(new GenericIdentity("User"), null);
var ticket = new AuthenticationTicket(user, new AuthenticationProperties(), Options.AuthenticationScheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
else
{
return Task.FromResult(AuthenticateResult.Fail("No valid user."));
}
}
this.Response.Headers["WWW-Authenticate"]= "Basic realm=\"yourawesomesite.net\"";
return Task.FromResult(AuthenticateResult.Fail("No credentials."));
}
}
public class BasicAuthenticationMiddleware : AuthenticationMiddleware<BasicAuthenticationOptions>
{
public BasicAuthenticationMiddleware(
RequestDelegate next,
IOptions<BasicAuthenticationOptions> options,
ILoggerFactory loggerFactory,
UrlEncoder encoder)
: base(next, options, loggerFactory, encoder)
{
}
protected override AuthenticationHandler<BasicAuthenticationOptions> CreateHandler()
{
return new BasicAuthenticationHandler();
}
}
public class BasicAuthenticationOptions : AuthenticationOptions
{
public BasicAuthenticationOptions()
{
AuthenticationScheme = "Basic";
AutomaticAuthenticate = true;
}
}
Registration at Startup.cs - app.UseMiddleware<BasicAuthenticationMiddleware>();. With this code, you can restrict any controller with standart attribute Autorize:
[Authorize(ActiveAuthenticationSchemes = "Basic")]
[Route("api/[controller]")]
public class ValuesController : Controller
and use attribute AllowAnonymous if you apply authorize filter on application level.
As rightly said by previous posts, one of way is to implement a custom basic authentication middleware. I found the best working code with explanation in this blog:
Basic Auth with custom middleware
I referred the same blog but had to do 2 adaptations:
While adding the middleware in startup file -> Configure function, always add custom middleware before adding app.UseMvc().
While reading the username, password from appsettings.json file, add static read only property in Startup file. Then read from appsettings.json. Finally, read the values from anywhere in the project. Example:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public static string UserNameFromAppSettings { get; private set; }
public static string PasswordFromAppSettings { get; private set; }
//set username and password from appsettings.json
UserNameFromAppSettings = Configuration.GetSection("BasicAuth").GetSection("UserName").Value;
PasswordFromAppSettings = Configuration.GetSection("BasicAuth").GetSection("Password").Value;
}
You can use an ActionFilterAttribute
public class BasicAuthAttribute : ActionFilterAttribute
{
public string BasicRealm { get; set; }
protected NetworkCredential Nc { get; set; }
public BasicAuthAttribute(string user,string pass)
{
this.Nc = new NetworkCredential(user,pass);
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.HttpContext.Request;
var auth = req.Headers["Authorization"].ToString();
if (!String.IsNullOrEmpty(auth))
{
var cred = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(auth.Substring(6)))
.Split(':');
var user = new {Name = cred[0], Pass = cred[1]};
if (user.Name == Nc.UserName && user.Pass == Nc.Password) return;
}
filterContext.HttpContext.Response.Headers.Add("WWW-Authenticate",
String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
filterContext.Result = new UnauthorizedResult();
}
}
and add the attribute to your controller
[BasicAuth("USR", "MyPassword")]
In this public Github repo
https://github.com/boskjoett/BasicAuthWebApi
you can see a simple example of a ASP.NET Core 2.2 web API with endpoints protected by Basic Authentication.
ASP.NET Core 2.0 with Angular
https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login
Make sure to use type of authentication filter
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Asp.Net MVC Core and Identity 3

I'm got a site setup using vnext/core 1.0 that uses Identity 3 for authentication. I can create users, I can change passwords, I can login fine. The issue is, it appears to be ignoring the ExpireTimespan property as I'm randomly kicked out of the app after a certain amount of time and I'm struggling to get to the bottom of it.
I have my own userstore and usermanager
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
services.AddIdentity<Domain.Models.User, Domain.Models.UserRole>()
.AddUserStore<UserStore>()
.AddRoleStore<RoleStore>()
.AddUserManager<MyUserManager>()
.AddDefaultTokenProviders();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseMyIdentity();
...
}
public static IApplicationBuilder UseMyIdentity(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
var marker = app.ApplicationServices.GetService<IdentityMarkerService>();
if (marker == null)
{
throw new InvalidOperationException("MustCallAddIdentity");
}
var options = app.ApplicationServices.GetRequiredService<IOptions<IdentityOptions>>().Value;
app.UseCookieAuthentication(options.Cookies.ExternalCookie);
app.UseCookieAuthentication(options.Cookies.TwoFactorRememberMeCookie);
app.UseCookieAuthentication(options.Cookies.TwoFactorUserIdCookie);
CookieAuthenticationOptions appCookie = options.Cookies.ApplicationCookie;
appCookie.LoginPath = new Microsoft.AspNet.Http.PathString("/Login");
appCookie.SlidingExpiration = true;
appCookie.ExpireTimeSpan = TimeSpan.FromHours(8);
appCookie.CookieName = "MyWebApp";
app.UseCookieAuthentication(appCookie);
return app;
}
Login controller
var user = await userManager.FindByNameAsync(model.Username);
if (user != null)
{
SignInResult result = await signInManager.PasswordSignInAsync(user, model.Password, false, false);
if (result.Succeeded)
{
RedirectToActionPermanent("Index", "Home");
}
}
See my problem here:
ASP.NET Core 1.0 - MVC 6 - Cookie Expiration
I ran into the same problem and spent hours going through the OS-code of aspnet Identity on github :-)
Your custom UserManager has to implement Get/UpdateSecurityStampAsync
public class MyUserManager:UserManager<MinervaUser>
{
...
public override bool SupportsUserSecurityStamp
{
get
{
return true;
}
}
public override async Task<string> GetSecurityStampAsync(MinervaUser user)
{
// Todo: Implement something useful here!
return "Token";
}
public override async Task<IdentityResult> UpdateSecurityStampAsync(MinervaUser user)
{
// Todo: Implement something useful here!
return IdentityResult.Success;
}

Categories

Resources