Trying to understand how JWT works for asp.net core application. I use a ASP.NET MVC Core application template.
My StartUp.cs cotains configuration for JWT Token :
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
//options.EventsType = typeof(AuthenticateCustomEvent);
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
And my login controller contains code to return token on successfull validation of the user this returns a token as shown below
Once i recieve the token i make a call to a controller with [Authorize] attribute applied to it with the bearer token from PostMan the controller never gets hit ? Am i missing something ?
My below solution is bit different, but this solution will help you to deal with custom auth implementation, you can implement a different type of auth for the different type of users. You require to create a class AuthorizationRequiredAttribute under your API project, this class will inherit ActionFilterAttribute class to filter each API request. you can filter all HTTP methods (GET, POST, PUT, DELETE...etc), and can implement your own authorization logic for specific HTTP method.
ActionFilterAttribute.cs
using BillSyatemCore.Common.Constants;
using BillSyatemCore.Services.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
using System.Net.Http;
namespace BillSyatemCore.Handlers
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class AuthorizationRequiredAttribute : ActionFilterAttribute
{
private IConfiguration _config;
public AuthorizationRequiredAttribute(IConfiguration config)
{
_config = config;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
try
{
if (context.HttpContext.Request.Headers.ContainsKey(Constants.HttpHeaders.Token))
{
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadToken(context.HttpContext.Request.Headers[Constants.HttpHeaders.Token])
as JwtSecurityToken;
var expireDate = Convert.ToDateTime(token.Claims.First(claim => claim.Type == Constants.JwtClaims.ExpiresOn).Value);
if (context.HttpContext.Request.Method == WebRequestMethods.Http.Get)
{
if (expireDate < DateTime.Now)
{
context.Result = new UnauthorizedResult();
}
}
else
{
//You may filter post,put,delete etc request here.
}
}
else
{
context.Result = new NotFoundResult();
}
}
catch (Exception ex)
{
context.Result = new BadRequestResult();
}
base.OnActionExecuting(context);
}
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//JWT
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.Build());
});
}
Controller.cs
using BillSyatemCore.Models.Authentication;
using BillSystemCore.Transporter;
using Microsoft.AspNetCore.Mvc;
namespace TestProject.Controllers
{
[Produces("application/json")]
[Route("api/[controller]")]
public class UserTypeController : Controller
{
private readonly IAuthTransporter _authTransporter;
public UserTypeController(IAuthTransporter authTransporter)
{
_authTransporter = authTransporter;
}
[HttpPost("save"), ServiceFilter(typeof(AuthorizationRequiredAttribute))]
public IActionResult Save([FromBody] UserType userType)
{
return Ok(_authTransporter.UserTypeServices.Save(userType));
}
}
}
Related
Not everytime but some time i get error while i try to get HttpContext.Session.GetString("userid");
I am using .Net core 2.2
Below is my session helper code
public static class SessionHelper
{
private static HttpContext _context = null;
public static void SetCurrentContext(HttpContext context)
{
_context = context;
}
public static string UserID
{
get
{
try
{
return _context.Session.GetString("userid");
}
catch
{
return "";
}
}
set
{
_context.Session.SetString("userid", value);
}
}
}
Below in my middleware configuration in Configure method startup.cs
app.UseSession();
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Frame-Options", "sameorigin");
await next();
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseMvcWithDefaultRoute();
Below is my ConfigureServices code of startup.cs
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(60 * 30);
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest;
//options.IOTimeout = TimeSpan.FromSeconds(60 * 30);
});
services.AddMvc()
.AddSessionStateTempDataProvider();
I have a working web API which response to a GET request and returns a list of reports. I recently implemented Odata and options such as $filter and $top work when I use $count it appears to be ignored.
I have read through multiple blogs and forms but I have not been able to get it work.
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o =>
{
o.ReturnHttpNotAcceptable = true;
o.EnableEndpointRouting = false;
});
// Connection string from environmental variables
var connectionString = Configuration["connectionString:077test"];
// Connection to SQL
services.AddDbContext<ServiceCatalogContext>(option => option.UseSqlServer(connectionString));
// Scope
services.AddScoped<IReportsRepo, ReportsRepo>();
services.AddControllers();
services.AddOData();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Run(async context =>
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync("An unexpected fault happened. Try again later.");
});
});
}
//app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseStatusCodePages();
//app.UseMvc();
//expanding app.UseMvc for oData
app.UseMvc(routeBuilder =>
{
//routeBuilder.EnableDependencyInjection();
routeBuilder.Select()
.OrderBy()
.Filter()
.SkipToken()
.MaxTop(10)
.Expand()
.Count();
routeBuilder.MapODataServiceRoute("api", "api", GetEdmModel());
});
}
private static IEdmModel GetEdmModel()
{
var odataBuilder = new ODataConventionModelBuilder();
odataBuilder.EntitySet<ReportsDto>("reports");
return odataBuilder.GetEdmModel();
}
}
ReportsController.cs
public class ReportsController : ODataController //Controller //ControllerBase
{
private readonly IReportsRepo _reportsRepo;
public ReportsController(IReportsRepo reportsRepo, IMapper mapper)
{
_reportsRepo = reportsRepo ??
throw new ArgumentNullException(nameof(reportsRepo)); //handle if resultsRepo is null
}
[HttpGet]
[EnableQuery()] //enabled OData querying
[ODataRoute("reports")]
public ActionResult<IQueryable<ReportsDto>> GetReports() //updated from IEnumerable to IQueryable when OData was added
{
var results = _reportsRepo.GetReports();
//return Ok(_mapper.Map<IEnumerable<ReportsDto>>(results)); //mapper not working with OData if I change IEnumerable to IQueryable
return Ok(results);
}
}
The API returns results and I can use $filter and $top but I just cannot figure out how to get $count working.
Any help with my specific API would be greatly appreciated!
Use app.UseEndpoints() instead of app.UseMvc(). The equivalent of your UseMvc() would be:
app.UseEndpoints(endpoints =>
{
//endpoints.EnableDependencyInjection();
endpoints.Select()
.OrderBy()
.Filter()
.SkipToken()
.MaxTop(10)
.Expand()
.Count();
endpoints.MapODataRoute("api", "api", GetEdmModel());
});
I have written a basic WebAPI that authenticates using Jwt Tokens. When I make a test API call using Postman I get a token issued successfully. I am however stuck in getting my MVC application to authenticate using the tokens.
Here is the controller for the API -
[HttpPost]
[Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel model)
{
var user = await userManager.FindByNameAsync(model.Username);
if (user != null && await userManager.CheckPasswordAsync(user, model.Password))
{
var authClaims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("YVBy0OLlMQG6VVVp1OH7Xzyr7gHuw1qvUC5dcGt3SBM="));
var token = new JwtSecurityToken(
issuer: "https://localhost:44350",
audience: "https://localhost:44350",
expires: DateTime.Now.AddHours(3),
claims: authClaims,
signingCredentials: new Microsoft.IdentityModel.Tokens.SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
Here is the Startup.cs for the WebAPI -
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.AddControllers();
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "https://localhost:44350/",
ValidIssuer = "https://localhost:44350/",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("AA873344gshtrhjLJKJSLKF8u4o8grwieot4KJHFs9847GGSD"))
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
SeedDB.Initialize(app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope().ServiceProvider);
}
}
My code for the MVC applicaiton controller is below. It generates a token successfully, but I cant work out how to populate HttpContext.User.Identity?
[HttpPost]
public async Task<ActionResult> Index(LoginModel login)
{
string url = BaseUrl + "api/authenticate/login";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(url);
var postTask = client.PostAsJsonAsync<LoginModel>("login", login);
postTask.Wait();
var result = postTask.Result;
if (result.IsSuccessStatusCode)
{
var user = HttpContext.User.Identity as ClaimsIdentity;
var tokenDetails = JsonConvert.DeserializeObject<Dictionary<string, string>>(result.Content.ReadAsStringAsync().Result);
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, login.Username, "string"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.Ticks.ToString(), ClaimValueTypes.Integer64)
};
user.AddClaims(claims);
return RedirectToAction("Index", "Home", null);
}
}
ModelState.AddModelError(string.Empty, "Server error");
return View(login);
}
Here is the Startup.cs for the MVC application
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.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
You should add authentication service and use authentication middleware in the startup.cs:
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(option =>
{
option.RequireHttpsMetadata = false;
option.SaveToken = true;
option.TokenValidationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
ValidateLifetime = true,
ValidIssuer = "Some Issuer",
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "Some Audience",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Security Key")),
ValidateIssuerSigningKey = true,
};
});
Also add app.UseAuthentication(); to the Configure method in startup.cs.
I have an API controller hosted in an area. However, the routing doesn't seem to be working as my ajax calls keep returning 404's when trying to hit the controller actions. Breakpoints in the controller constructor are never hit.
[Area("WorldBuilder")]
[Route("api/[controller]")]
[ApiController]
public class WorldApiController : ControllerBase
{
IWorldService _worldService;
IUserRepository _userRepository;
public WorldApiController(IWorldService worldService, IUserRepository userRepository)
{
_worldService = worldService;
_userRepository = userRepository;
}
[HttpGet]
public ActionResult<WorldIndexViewModel> RegionSetSearch()
{
string searchTerm = null;
var userId = User.GetUserId();
WorldIndexViewModel model = new WorldIndexViewModel();
IEnumerable<UserModel> users = _userRepository.GetUsers();
UserModel defaultUser = new UserModel(new Microsoft.AspNetCore.Identity.IdentityUser("UNKNOWN"), new List<Claim>());
model.OwnedRegionSets = _worldService.GetOwnedRegionSets(userId, searchTerm);
var editableRegionSets = _worldService.GetEditableRegionSets(userId, searchTerm);
if (editableRegionSets != null)
{
model.EditableRegionSets = editableRegionSets.GroupBy(rs =>
(users.FirstOrDefault(u => u.IdentityUser.Id == rs.OwnerId) ?? defaultUser)
.IdentityUser.UserName)
.Select(g => new RegionSetCollectionModel(g)).ToList();
}
var viewableRegionSets = _worldService.GetViewableRegionSets(userId, searchTerm);
if (viewableRegionSets != null)
{
model.ViewableRegionSets = viewableRegionSets.Where(vrs => vrs.OwnerId != userId).GroupBy(rs =>
(users.FirstOrDefault(u => u.IdentityUser.Id == rs.OwnerId) ?? defaultUser)
.IdentityUser.UserName)
.Select(g => new RegionSetCollectionModel(g)).ToList();
}
return model;
}
}
And my startup.cs file:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(name: "areaRoute",
template: "{area}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
}
}
I have tried the following ajax addresses:
localhost:44344/api/WorldApi/RegionSetSearch
localhost:44344/WorldBuilder/api/WorldApi/RegionSetSearch
localhost:44344/api/WorldBuilder/WorldApi/RegionSetSearch
localhost:44344/WorldBuilder/WorldApi/RegionSetSerarch
For the last address I tried, I removed the "api/" from the Route data annotation on the controller.
I'm not sure what I'm doing wrong here. I'm following all of the examples I've found online.
There are two routing type in MVC, conventions routing which is for mvc and route attribute routing which is for web api.
For area which is configured in conventions routings for MVC should not be combine with route attribute. Route attribute will override the default convention routing.
If you prefer attribute routing, you could
[Route("WorldBuilder/api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet("RegionSetSearch")]
public ActionResult<IEnumerable<string>> RegionSetSearch()
{
return new string[] { "value1", "value2" };
}
}
Pay attention to [HttpGet("RegionSetSearch")] which define the action for RegionSetSearch and append a placeholder in the url.
The Reuqest is https://localhost:44389/worldbuilder/api/values/RegionSetSearch
If you prefer conventions routing, you could remove Route and ApiController like
[Area("WorldBuilder")]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> RegionSetSearch()
{
return new string[] { "value1", "value2" };
}
}
With this way, you need to change the UseMvc like
app.UseMvc(routes => {
routes.MapRoute("areaRoute", "{area:exists}/api/{controller}/{action}/{id?}");
});
And the request is https://localhost:44389/worldbuilder/api/values/RegionSetSearch
I want to filter the range of client IPs who can route to Prometheus metrics.
So in startup I have
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.UsePrometheusServer(q =>
{
q.MapPath = "/metrics";
});
app.UseWebApi(config);
}
And this is my custom actionFilter class
public class IpFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
string clinetIP = GetClientIpAddress(actionContext.HttpContext.Items["MS_HttpRequestMessage"] as HttpRequestMessage);
if (IpAllowed(clinetIP))
base.OnActionExecuting(actionContext);
}
But I have no idea how to use IpFilter since it cannot be use as an attribute on a controller action.
I tried to use it by adding a middleware using owin but the next.Invoke doesn't work properly
public void Configuration(IAppBuilder app)
{
app.Map("/metrics", metricsApp =>
{
metricsApp.Use<TestIpMid>(deniedIps);
metricsApp.UsePrometheusServer(q => q.MapPath = "/metrics");
});
app.UsePrometheusServer(q =>
{
q.MapPath = "/metrics";
});
app.UseWebApi(config);
}
and this is the middleware:
public class TestIpMid : OwinMiddleware
{
private readonly HashSet<string> _deniedIps;
public TestIpMid(OwinMiddleware next, HashSet<string> deniedIps) : base(next)
{
_deniedIps = deniedIps;
}
public override async Task Invoke(IOwinContext context)
{
var ipAddress = context.Request.RemoteIpAddress;
if (_deniedIps.Contains(ipAddress))
{
context.Response.StatusCode = 403;
return;
}
await Next.Invoke(context);
}
}
please help me :'(
this solution worked for me but other ways I was thinking of didn't work
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
var config = new HttpConfiguration();
var allowedIps = ProtectedSettings.Read(ProtectedSettings.protheusIpWhitelist).Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
app.Use(async (Context, next) =>
{
var ipAddress = Context.Request.RemoteIpAddress;
if ((!allowedIps.Contains(ipAddress)) && Context.Request.Path.Value == "/metrics")
{
Context.Response.StatusCode = 403;
return;
}
await next.Invoke();
});
app.UsePrometheusServer(q =>
{
q.MapPath = "/metrics";
});
app.UseWebApi(config);
}