Core 2 Identity - Get ASPNetUsers.Id - c#

I have an MVC App I am adding API controllers to.
I have Authentication and Authorization through Cookie and bearer token (JWT). For MVC controllers it is using cookie. for API controllers a token.
I am trying to link the data together which is all keyed based upon the UserID.
The MVC App's implementation of
var userid = _userManager.GetUserId(User);
Returns the ASPNetUsers.Id value. So when using the app all data gets written away with the GUID as the OwnerID.
Now on my API controller, I have exactly the same line of code.
var userid = _userManager.GetUserId(User);
However, this is returning the username and not the User ID.
I need it to return the same value, the guid on ASPNetUsers.Id.
Any ideas? You can see below some things I've been trying.
Controller
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TechsportiseOnline.Data;
using TechsportiseOnline.Helpers;
using TechsportiseOnline.Models;
namespace TechsportiseOnline.Controllers
{
/// <summary>
/// This class is used as an API for Races
/// </summary>
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
public class RaceController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IAuthorizationService _authorizationService;
private Task<ApplicationUser> GetCurrentUserAsync() => _userManager.GetUserAsync(User);
public RaceController(ApplicationDbContext context, IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager)
{
_context = context;
_userManager = userManager;
_authorizationService = authorizationService;
}
/// <summary>
/// Get all Races
/// </summary>
/// <remarks>
/// Gets all Races which have been created by the user
/// </remarks>
/// <returns>All created Races</returns>
[HttpGet]
public IEnumerable<Race> GetAll()
{
var test = User.Identity.IsAuthenticated;
var userid = _userManager.GetUserId(User);
var user = GetCurrentUserAsync();
//Get only records where the OwnerId is not the logged in User.
//return _context.Races.Where(p => p.OwnerID == User.GetUserId()).ToList();
return _context.Races.ToList();
}
/// <summary>
/// Get a single Race
/// </summary>
/// <remarks>
/// Gets the details from a single Race from it's ID
/// </remarks>
/// <param name="id">Race ID</param>
/// <returns>Single Race</returns>
[HttpGet("{id}", Name = "GetRace")]
public IActionResult GetById(long id)
{
//Only return the data when it is owned by the same Id
//var item = _context.Races.Where(p => p.OwnerID == User.GetUserId()).FirstOrDefault(t => t.ID == id);
var item = _context.Races.FirstOrDefault(t => t.ID == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
/// <summary>
/// Get all entries for a Race
/// </summary>
/// <remarks>
/// Gets the all the entries from the race ID
/// </remarks>
/// <param name="id">Race ID</param>
/// <returns>All Entries from the given Race ID</returns>
[HttpGet("{id}/entries", Name = "GetEntriesByRaceID")]
public IEnumerable<RaceEntry> GetAllEntries(long id)
{
//Only return the data when it is owned by the same Id
//Get only records where the OwnerId is not the logged in User.
return _context.RaceEntries
//return _context.RaceEntries.Where(p => p.OwnerID == User.GetUserId())
.Where(p => p.RaceID == id)
.ToList();
}
///// <summary>
///// Get all timings for a Race
///// </summary>
///// <remarks>
///// Gets the all the timings from the race ID
///// </remarks>
///// <param name="id">Race ID</param>
///// <returns>All timings from the given Race ID</returns>
//[HttpGet("{id}/timings", Name = "GetTimingsByRaceID")]
//public IEnumerable<Timing> GetAllTimings(long id)
//{
// //Only return the data when it is owned by the same Id
// //Get only records where the OwnerId is not the logged in User.
// return _context.Timings.Where(p => p.OwnerId == User.GetUserId())
// .Where(p => p.RaceId == id)
// .ToList();
//}
///// <summary>
///// Get the results for a Race
///// </summary>
///// <remarks>
///// Gets the all the results from the race ID
///// </remarks>
///// <param name="id">Race ID</param>
///// <returns>All results from the given Race ID</returns>
//[HttpGet("{id}/results", Name = "GetResultsByRaceID")]
//public IEnumerable<Results> GetAllResults(long id)
//{
// List<Results> raceresults = new List<Results>();
// var raceid = id;
// foreach (var raceentry in _context.RaceEntries.Where(p => p.OwnerId == User.GetUserId())
// .Where(p => p.RaceID == id))
// {
// var raceresult = new Results();
// var racedetails = _context.Races.Where(t => t.OwnerId == User.GetUserId())
// .FirstOrDefault(t => t.Id == raceid);
// var timingdetails = _context.Timings.Where(t => t.OwnerId == User.GetUserId())
// .FirstOrDefault(t => t.BibNumber == raceentry.BibNumber);
// var race = _context.Races.Where(t => t.OwnerId == User.GetUserId())
// .FirstOrDefault(t => t.Id == id);
// raceresult.AthleteUserID = raceentry.AthleteUserId;
// raceresult.Category = "Category";
// raceresult.CategoryPosition = 1;
// raceresult.ChipTime = DateTime.Now; //timingdetails.EndTime - timingdetails.StartTime;
// raceresult.Club = raceentry.Club;
// raceresult.ClubPosition = 1;
// raceresult.EntryId = raceentry.Id;
// raceresult.FirstName = raceentry.FirstName;
// raceresult.Gender = raceentry.Gender;
// raceresult.GenderPosition = 1;
// raceresult.GunTime = DateTime.Now; //race.RaceStartTime - timingdetails.EndTime;
// raceresult.LastName = raceentry.LastName;
// raceresult.OverallPosition = 0;
// raceresult.RaceDate = race.RaceDate;
// raceresult.RaceID = raceid;
// raceresult.RaceName = race.Name;
// raceresult.ResultId = 1;
// raceresult.Team = raceentry.Team;
// raceresult.TeamPosition = 1;
// raceresults.Add(raceresult);
// //build result object
// }
// //Only return the data when it is owned by the same Id
// //Get only records where the OwnerId is not the logged in User.
// return raceresults.ToList();
//}
///// <summary>
///// Publish the results of a Race
///// </summary>
///// <remarks>
///// Publishes the results as Provisional or Final. Final will submit them to RunBritain/PO10
///// </remarks>
///// <returns>The JSON for the created Race</returns>
//[HttpPost("{id}/publish", Name = "PublishResults")]
//public IActionResult Publish([FromBody] Race item)
//{
// if (item == null)
// {
// return BadRequest();
// }
// _context.Races.Add(item);
// //Set Owner ID
// item.OwnerId = User.GetUserId();
// _context.SaveChanges();
// return CreatedAtRoute("GetRace", new { id = item.Id }, item);
//}
/// <summary>
/// Creates a Race
/// </summary>
/// <remarks>
/// Creates a Race which can have entrants and timings assigned to it.
/// </remarks>
[HttpPost]
public IActionResult Create([FromBody] RacePost item)
{
if (item == null)
{
return BadRequest();
}
if (item.Name == null)
{
return BadRequest("The Race must have a Name");
}
var raceitem = new Race
{
CurrentEntries = item.CurrentEntries,
Description = item.Description,
MaxEntries = item.MaxEntries,
Name = item.Name,
ContactName = item.ContactName,
ContactEmail = item.ContactEmail,
ContactNumber = item.ContactNumber,
//OwnerID = User.GetUserId(),
RaceDate = item.RaceDate,
RaceStartTime = item.RaceStartTime,
IsCompleted = item.IsCompleted,
IsPublished = item.IsPublished,
IsOpenForEntries = item.IsOpenForEntries,
LastUpdated = DateTime.Now
};
_context.Races.Add(raceitem);
_context.SaveChanges();
return CreatedAtRoute("GetRace", new { id = raceitem.ID }, raceitem);
}
/// <summary>
/// Update a Race
/// </summary>
/// <remarks>
/// Update's a Race's details
/// </remarks>
/// <param name="id">Race ID</param>
/// <returns>The JSON for the updated Race</returns>
[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] Race item)
{
if (item == null)
{
return BadRequest();
}
if (item.Name == null)
{
return BadRequest("The Race must have a Name");
}
//var race = _context.Races.Where(t => t.OwnerID == User.GetUserId())
var race = _context.Races
.FirstOrDefault(t => t.ID == id);
//var race = _context.Races.FirstOrDefault(t => t.ID == id);
if (race == null)
{
return NotFound();
}
//race.OwnerID = User.GetUserId();
race.Name = item.Name;
race.ContactName = item.ContactName;
race.ContactEmail = item.ContactEmail;
race.ContactNumber = item.ContactNumber;
race.RaceDate = item.RaceDate;
race.RaceStartTime = item.RaceStartTime;
race.Description = item.Description;
race.MaxEntries = item.MaxEntries;
race.CurrentEntries = item.CurrentEntries;
race.IsCompleted = item.IsCompleted;
race.IsPublished = item.IsPublished;
race.IsOpenForEntries = item.IsOpenForEntries;
race.LastUpdated = DateTime.Now;
_context.Races.Update(race);
_context.SaveChanges();
return new NoContentResult();
}
/// <summary>
/// Delete a Race
/// </summary>
/// <remarks>
/// Deletes a Race. Note: This will orphan any related result data and is not recommended!
/// </remarks>
/// <param name="id">Race ID</param>
/// <returns></returns>
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
//var race = _context.Races.Where(p => p.OwnerID == User.GetUserId()).FirstOrDefault(t => t.ID == id);
var race = _context.Races.FirstOrDefault(t => t.ID == id);
//var race = _context.Races.FirstOrDefault(t => t.Id == id);
if (race == null)
{
return NotFound();
}
var raceid = race.ID;
////Delete associated race entries
//foreach (var raceentry in _context.RaceEntries.Where(p => p.OwnerId == User.GetUserId())
// .Where(p => p.RaceID == raceid))
//{
// _context.RaceEntries.Remove(raceentry);
//}
////Delete associated race timings
//foreach (var timing in _context.Timings.Where(p => p.OwnerId == User.GetUserId())
// .Where(p => p.RaceId == raceid))
//{
// _context.Timings.Remove(timing);
//}
//Delete/Save the deletion of the race
_context.SaveChanges();
return new NoContentResult();
}
}
}
And Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TechsportiseOnline.Data;
using TechsportiseOnline.Models;
using TechsportiseOnline.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using TechsportiseOnline.Authorization;
using TechsportiseOnline.Helpers;
using Swashbuckle.AspNetCore.Swagger;
using System.IO;
using Microsoft.Extensions.PlatformAbstractions;
using static TechsportiseOnline.Helpers.Swagger;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace TechsportiseOnline
{
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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("TechsportiseDB")));
//options.UseInMemoryDatabase("Teschsportise"));
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 2;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
services.Configure<AuthMessageSenderOptions>(Configuration);
//services.ConfigureApplicationCookie(options =>
//{
// // Cookie settings
// options.Cookie.HttpOnly = true;
// options.Cookie.Expiration = TimeSpan.FromDays(150);
// options.LoginPath = "/Account/Login"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login
// options.LogoutPath = "/Account/Logout"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout
// options.AccessDeniedPath = "/Account/AccessDenied"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /Account/AccessDenied
// options.SlidingExpiration = true;
//});
// Add application services.
services.AddTransient<IEmailSender, Email>();
//services.AddTransient<ICreateContact>();
//services.AddTransient<IUpdateContact>();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Techsportise API", Version = "v1" });
c.OperationFilter<AddRequiredHeaderParameter>();
var filePath = Path.Combine(PlatformServices.Default.Application.ApplicationBasePath, "Techsportise.xml");
c.IncludeXmlComments(filePath);
});
services.Configure<JWTSettings>(Configuration.GetSection("JWTSettings"));
services.AddAuthentication()
.AddCookie()
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.IncludeErrorDetails = true;
var secretKey = Configuration.GetSection("JWTSettings:SecretKey").Value;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = Configuration.GetSection("JWTSettings:Issuer").Value,
ValidateAudience = true,
ValidAudience = Configuration.GetSection("JWTSettings:Audience").Value,
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
};
});
services.AddMvc();
var skipSSL = Configuration.GetValue<bool>("LocalTest:skipSSL");
// requires using Microsoft.AspNetCore.Mvc;
services.Configure<MvcOptions>(options =>
{
// Set LocalTest:skipSSL to true to skip SSL requrement in
// debug mode. This is useful when not using Visual Studio.
if (!skipSSL)
{
options.Filters.Add(new RequireHttpsAttribute());
}
});
//services.AddMvc(config =>
//{
// var policy = new AuthorizationPolicyBuilder()
// .RequireAuthenticatedUser()
// .Build();
// config.Filters.Add(new AuthorizeFilter(policy));
//});
services.AddScoped<IAuthorizationHandler,
OwnerRaceAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
AdminRaceAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler,
OwnerRaceEntriesAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
AdminRaceEntriesAuthorizationHandler>();
}
// 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.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Techsportise API V1");
});
}
}
}
Edit: Here's the TokenController generating the token
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using TechsportiseOnline.Models.AccountViewModels;
using Microsoft.AspNetCore.Identity;
using TechsportiseOnline.Models;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using TechsportiseOnline.Helpers;
namespace TechsportiseOnline.Controllers
{
[Produces("application/json")]
[Route("api/Token")]
public class TokenController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IConfiguration _configuration;
private readonly IOptions<JWTSettings> _jwtConfig;
public TokenController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IConfiguration configuration,
IOptions<JWTSettings> jwtConfig)
{
_userManager = userManager;
_signInManager = signInManager;
_configuration = configuration;
_jwtConfig = jwtConfig;
}
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> GenerateToken([FromBody] LoginViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(model.Email);
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (result.Succeeded)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_jwtConfig.Value.SecretKey.ToString()));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_jwtConfig.Value.Issuer.ToString(),
_jwtConfig.Value.Audience.ToString(),
claims,
expires: DateTime.Now.AddDays(30),
signingCredentials: creds);
// return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
return new JsonResult(new Dictionary<string, object>
{
{ "access_token", new JwtSecurityTokenHandler().WriteToken(token) },
});
}
}
}
return BadRequest("Could not create token");
}
}
}

Thanks to Kirk, I was able to rather quickly see the fix after he asked his question and I looked at my token generator controller.
In my token I was generating the claim
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
Changing that to
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
Solved it.

Related

What causes my date to come in this format: /Date(1640035318903)/ in C# from the controller?

My records are coming back from the server in C# in this format:
(via network tab / preview on the network call)
DateModified: "/Date(1640035318903)/"
What would cause this issue? Why won't it just come back like this?
This is how I'm returning it via my controller via a post:
/// <summary>
/// Gets the application file manager file system items.
/// </summary>
/// <param name="fileManagerJsParam">The file manager js parameter.</param>
/// <returns>
/// ActionResult
/// </returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> GetFileManagerFileSystemItems(FileManagerJsParamModel fileManagerJsParam)
{
if (fileManagerJsParam == null)
{
throw new ArgumentNullException(nameof(fileManagerJsParam));
}
if (!ModelState.IsValid)
{
return Json(new
{
success = false,
errorMessage = string.Join(
",",
ModelState.Values.Where(e => e.Errors.Count > 0)
.SelectMany(e => e.Errors)
.Select(e => e.ErrorMessage)
.ToArray())
});
}
var csModel = mapper.Map<FileManagerCsParamModel>(fileManagerJsParam);
var result = await fileManagerUtilities.GetApplicationFileManagerFileSystemItems(csModel, 107);
return Json(result.FileSystemItems);
}
Here is my javascript that calls it:
function getItemsFileManager(e) {
var deferred = $.Deferred()
let fullUrl = baseEndPoint + loadAction;
// check to see if we have different base end point / load action urls
if (e && e.dataItem) {
var fileManagerAPIItems = getFileManagerAPIItems(e.dataItem);
if (fileManagerAPIItems) {
fullUrl = fileManagerAPIItems.BaseEndPoint + fileManagerAPIItems.LoadAction;
}
}
let fileManagerJsParam = getFileManagerParams(e, true);
if (!fileManagerJsParam) {
console.log("fileManagerParms not set in getItemsFileManager # " + new Date());
genericErrorMessage();
return;
}
$.post(BuildSafeURL(fullUrl, null), fileManagerJsParam)
.done(function (data) {
deferred.resolve(data);
})
.fail(function (error) {
deferred.reject(getItemGenericErrorMessage(e));
if (error && error.responseJSON && error.responseJSON.errorMessage) {
console.log("error⚠️", error.responseJSON.errorMessage);
}
else {
console.log("error⚠️", error);
}
});
return deferred.promise();
}
I found the result from this post: https://stackoverflow.com/a/9302054 (Approach 2).
I had to update all my controller http post calls to return Task<JsonResult> / return new CustomJsonResult { Data = result.FileSystemItems };
Here is the code:
namespace WebPortal.MVC.Utilities
{
using System;
using System.Web;
using System.Web.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
/// <summary>
/// CustomJsonResult
/// </summary>
/// <seealso cref="JsonResult" />
public class CustomJsonResult : JsonResult
{
private const string DateFormat = "yyyy-MM-ddTHH:mm:ssZ";
/// <summary>
/// Executes the result.
/// </summary>
/// <param name="context">The context.</param>
/// <exception cref="System.ArgumentNullException">context</exception>
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
if (!string.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
// Using Json.NET serializer
var isoConvert = new IsoDateTimeConverter();
isoConvert.DateTimeFormat = DateFormat;
response.Write(JsonConvert.SerializeObject(Data, isoConvert));
}
}
}
}
/// <summary>
/// Gets the application file manager file system items.
/// </summary>
/// <param name="fileManagerJsParam">The file manager js parameter.</param>
/// <returns>
/// ActionResult
/// </returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<JsonResult> GetFileManagerFileSystemItems(FileManagerJsParamModel fileManagerJsParam)
{
if (fileManagerJsParam == null)
{
throw new ArgumentNullException(nameof(fileManagerJsParam));
}
if (!ModelState.IsValid)
{
return Json(new
{
success = false,
errorMessage = string.Join(
",",
ModelState.Values.Where(e => e.Errors.Count > 0)
.SelectMany(e => e.Errors)
.Select(e => e.ErrorMessage)
.ToArray())
});
}
var csModel = mapper.Map<FileManagerCsParamModel>(fileManagerJsParam);
var result = await fileManagerUtilities.GetApplicationFileManagerFileSystemItems(csModel, 107);
return new CustomJsonResult { Data = result.FileSystemItems };
}
Results:

Asp.net Core Email Sender is not working in Registration page

I'm trying to setup an asp.net core razor page app with using asp.net identity.
I have everything set up and I'm trying to generate the email for registration, but the code for:
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(value: callbackUrl)}'>clicking here</a>.").ConfigureAwait(false);
is not being hit. Like it won't even debug into it period and therefore my email for registration is not being sent or generated. The values are saving in my SQL Server database without any issues.
Here is the setup that I have going:
appsetting.js
Program.cs
public class Program
{
/// <summary>
/// Defines the entry point of the application.
/// </summary>
/// <param name="args">The arguments.</param>
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
try
{
logger.Debug("init main");
CreateHostBuilder(args).Build().Run();
}
catch (Exception exception)
{
// NLog: catch setup errors
logger.Error(exception, "Stopped program because of exception");
throw;
}
finally
{
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
NLog.LogManager.Shutdown();
}
}
/// <summary>
/// Creates the host builder.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns></returns>
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
})
.UseNLog(); // NLog: Setup NLog for Dependency injection
}
}
Starup.cs
/// <summary>
/// Startup
/// </summary>
public class Startup
{
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
/// <summary>
/// Gets the configuration.
/// </summary>
/// <value>
/// The configuration.
/// </value>
public IConfiguration Configuration { get; }
/// <summary>
/// Configures the services.
/// // This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services">The services.</param>
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services
.AddControllersWithViews()
.AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
// Added for identity set up - core27
services.AddRazorPages();
services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
services.Configure<SessionTimeoutSettings>(Configuration.GetSection("SessionTimeoutSettings"));
services.AddSingleton<IEmailSender, EmailSender>();
// not sure what this is for?
//services.AddDevExpressControls();
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.AddMaps(Assembly.GetExecutingAssembly());
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
});
var sessionTimeout = Convert.ToDouble(Configuration.GetSection("SessionTimeoutSettings").GetSection("SessionTimeout").Value);
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(sessionTimeout);
});
}
/// <summary>
/// Configures the specified application.
/// </summary>
/// <param name="app">The application.</param>
/// <param name="env">The env.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
// Added for identity set up - core27
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapDefaultControllerRoute();
endpoints.MapRazorPages();
});
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "node_modules")),
RequestPath = "/node_modules",
});
}
}
}
EmailSender.cs
#pragma warning disable SA1649 // File name should match first type name
public interface IEmailSender
#pragma warning restore SA1649 // File name should match first type name
{
/// <summary>
/// Sends the email asynchronous.
/// </summary>
/// <param name="email">The email.</param>
/// <param name="subject">The subject.</param>
/// <param name="message">The message.</param>
/// <returns>Task</returns>
Task SendEmailAsync(string email, string subject, string message);
}
/// <summary>
/// EmailSender
/// </summary>
/// <seealso cref="MORE.Website.Service.IEmailSender" />
public class EmailSender : IEmailSender
{
/// <summary>
/// The email settings
/// </summary>
private readonly EmailSettings emailSettings;
/// <summary>
/// The env
/// </summary>
private readonly IWebHostEnvironment env;
/// <summary>
/// Initializes a new instance of the <see cref="EmailSender"/> class.
/// </summary>
/// <param name="emailSettings">The email settings.</param>
/// <param name="env">The env.</param>
public EmailSender(
IOptions<EmailSettings> emailSettings,
IWebHostEnvironment env)
{
this.emailSettings = emailSettings.Value;
this.env = env;
}
/// <summary>
/// Sends the email asynchronous.
/// </summary>
/// <param name="email">The email.</param>
/// <param name="subject">The subject.</param>
/// <param name="message">The message.</param>
/// <exception cref="InvalidOperationException">InvalidOperationException</exception>
/// <returns>Task</returns>
public async Task SendEmailAsync(string email, string subject, string message)
{
try
{
var mimeMessage = new MimeMessage();
mimeMessage.From.Add(new MailboxAddress(emailSettings.SenderName, emailSettings.SenderFromAddress));
mimeMessage.To.Add(new MailboxAddress(email));
mimeMessage.Subject = subject;
mimeMessage.Body = new TextPart("html")
{
Text = message,
};
using (var client = new SmtpClient())
{
// For demo-purposes, accept all SSL certificates (in case the server supports STARTTLS)
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
if (env.IsDevelopment())
{
// The third parameter is useSSL (true if the client should make an SSL-wrapped
// connection to the server; otherwise, false).
// set to false for testing.
await client.ConnectAsync(emailSettings.MailServer, emailSettings.MailPort, false);
}
else
{
await client.ConnectAsync(emailSettings.MailServer);
}
// Note: only needed if the SMTP server requires authentication
await client.AuthenticateAsync(emailSettings.Sender, emailSettings.Password);
await client.SendAsync(mimeMessage);
await client.DisconnectAsync(true);
}
}
catch (Exception ex)
{
// TODO: handle exception
throw new InvalidOperationException(ex.Message);
}
}
}
}
Registration.cshtml
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Svc27M.Areas.Identity.Data;
using Svc27M.Services;
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<Svc27MUser> _signInManager;
private readonly UserManager<Svc27MUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<Svc27MUser> userManager,
SignInManager<Svc27MUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new Svc27MUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(value: callbackUrl)}'>clicking here</a>.").ConfigureAwait(false);
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
I figured it out:
There was an IEmailSender interface within:
using Microsoft.AspNetCore.Identity.UI.Services;
And my IEmailSender was not pointing to the local one I made.
Below is the change and the fix.
Emails are sending now properly for the Registration.
Here is my code change:

Localisation in Asp.net Core 2.0 not working

I have added localisation like below. However, its not displaying the relevant values.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add swagger here using extension methods.
services.AddSwaggerService();
// Add language support here.
// Default language is English. Supported
// languages are English and French.
services.AddLanguageSupport();
// Add controller
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization()
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
{
var assemblyName = new AssemblyName(typeof(SharedResource).GetTypeInfo().Assembly.FullName);
return factory.Create("SharedResource", assemblyName.Name);
};
})
.AddControllersAsServices();
}
The extension method for language support:
public static void AddLanguageSupport(this IServiceCollection services)
{
// Add list of supported cultures
// here. Two languages are English
// and french.
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.Configure<RequestLocalizationOptions>(
options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en"),
new CultureInfo("fr"),
};
options.DefaultRequestCulture = new RequestCulture(culture: "en", uiCulture: "en");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
}
The middleware:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(locOptions.Value);
}
In Resources folder, I have the following:
SharedResource class:
public class SharedResource
{
}
LocService class:
public class LocService
{
private readonly IStringLocalizer _localizer;
/// <summary>
/// Initializes a new instance of the <see cref="LocService"/> class.
/// </summary>
/// <param name="factory">factory.</param>
public LocService(IStringLocalizerFactory factory)
{
var type = typeof(SharedResource);
var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
_localizer = factory.Create("SharedResource", assemblyName.Name);
}
/// <summary>
/// returns localised string for a key.
/// </summary>
/// <param name="key">key.</param>
/// <returns>localised string.</returns>
public LocalizedString GetLocalizedHtmlString(string key)
{
return _localizer[key];
}
}
The Controller:
public class AuthController : ControllerBase
{
private readonly IStringLocalizer<SharedResource> _localizer;
public AuthController(IStringLocalizer<SharedResource> localizer)
{
_localizer = localizer;
}
public async Task<ActionResult> Post([FromBody] LoginModel login)
{
if (await _userService.IsUserAuthenticatedAsync(login.UserName, login.Password))
{
responseToken = await _userService.GetResponseToken(login.UserName);
statusCode = HttpStatusCode.OK;
tokenResponseMessage = new ResponseMessage(true, responseToken, new Message(statusCode));
// generate access token
response = Ok(tokenResponseMessage);
}
else
{
statusCode = HttpStatusCode.Unauthorized;
tokenResponseMessage = new ResponseMessage(false, null, new Message(statusCode,
_localizer["Login_InCorrectUserNamePassword"]));
response = Unauthorized(tokenResponseMessage);
}
}
return response;
}
_localizer["Login_InCorrectUserNamePassword"])) returns Login_InCorrectUserNamePassword in Swagger.
You could always go into the folder YourProject\obj\Debug\netcoreapp2.1 to see your resource file (for example, MyProjectName.Resources.SharedResource.en.resource).
Check the result of _localizer["Login_InCorrectUserNamePassword"] to find its value of SearchedLocation.It should match the file name of resource file.
In my case,the SharedResource.cs have a namespace like namespace MyProjectName.Resources instead of namespace MyProjectName which leads to wrong SearchedLocation ——MyProjectName.Resources.Resources.SharedResource since I have used
services.AddLocalization(options => options.ResourcesPath = "Resources");
Change its namespace to below and it works:
namespace MyProjectName
{
public class SharedResource
{
}
}

Core 2 Controller - HTTP Verb Not Allowed

I have a Controller I am using in a Restful API as part of a .NET Core 2 MVC app. I have pasted the code for the controller below.
Everything works great apart form the last method. [HttpDelete("Race/{raceid}")]. When I try and issue a DELETE from Postman or Swagger I get "HTTP verb used to access this page is not allowed"
I can't understand why. My standard Delete method works fine. This is just a second delete method. I have GET methods following a similar pattern and they work fine.
Any ideas?
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using System;
using TechsportiseOnline.Data;
using Microsoft.AspNetCore.Identity;
using TechsportiseOnline.Models;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace Techsportise.Controllers
{
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
public class TimingController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IAuthorizationService _authorizationService;
public TimingController(ApplicationDbContext context, IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager)
{
_context = context;
_userManager = userManager;
_authorizationService = authorizationService;
}
/// <summary>
/// Get all Race Timings by Race ID
/// </summary>
/// <remarks>
/// Gets all Race Timings which have been recorded by the user for the passed race
/// </remarks>s
/// <param name="raceid">Race ID</param>
/// <returns>All created Race Timings</returns>
[HttpGet("Race/{raceid}", Name = "GetTimingsByRaceID")]
public IEnumerable<Timing> GetAllByRaceID(long raceid)
{
//Get only records where the OwnerID is not the logged in User.
var alltimings = _context.Timings.Where(p => p.OwnerId == _userManager.GetUserId(User))
.Where(p => p.RaceId == raceid);
return alltimings.ToList();
}
/// <summary>
/// Get Count of Timings by Race ID
/// </summary>
/// <remarks>
/// Gets a count of all Race Timings which have been recorded by the user for the passed race
/// </remarks>s
/// <param name="raceid">Race ID</param>
/// <returns>All created Race Timings</returns>
[HttpGet("Race/{raceid}/count", Name = "GetTimingsCountByRaceID")]
public IActionResult GetCountByRaceID(long raceid)
{
//Get only records where the OwnerID is not the logged in User.
var count = _context.Timings.Where(p => p.OwnerId == _userManager.GetUserId(User))
.Where(p => p.RaceId == raceid)
.Count();
var response = new TimingCount()
{
RaceId = raceid,
TimingsCount = count
};
return new ObjectResult(response);
}
/// <summary>
/// Get a single Race Timing
/// </summary>
/// <remarks>
/// Gets the details from a single Race Timing from it's ID
/// </remarks>
/// <param name="id">Timing ID</param>
/// <returns>Single Race Timing</returns>
[HttpGet("{id}", Name = "GetTiming")]
public IActionResult GetById(long id)
{
var item = _context.Timings .Where(t => t.OwnerId == _userManager.GetUserId(User))
.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
/// <summary>
/// Create a new Race Timing
/// </summary>
/// <remarks>
/// Creates a new Race Timing
/// </remarks>
/// <returns>The JSON for the created Race Timing</returns>
[HttpPost]
public IActionResult Create([FromBody] TimingPost item)
{
if (item == null)
{
return BadRequest();
}
//Validate that the race ID exists
var race = _context.Races.Where(p => p.OwnerID == _userManager.GetUserId(User)).FirstOrDefault(t => t.ID == item.RaceId);
if (race == null)
{
return BadRequest("The RaceID you have sent does not exist");
}
var timingitem = new Timing();
timingitem.EndTime = item.EndTime;
timingitem.RaceId = item.RaceId;
timingitem.OwnerId = _userManager.GetUserId(User);
timingitem.Position = item.Position;
timingitem.StartTime = item.StartTime;
timingitem.LastUpdated = DateTime.Now;
_context.Timings.Add(timingitem);
_context.SaveChanges();
return CreatedAtRoute("GetTiming", new { id = timingitem.Id }, timingitem);
}
/// <summary>
/// Update a Race Timing
/// </summary>
/// <remarks>
/// Update's a Race Timing's details
/// </remarks>
/// <param name="id">Timing ID</param>
/// <returns>The JSON for the updated Race Timing</returns>
[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] Timing item)
{
if (item == null)
{
return BadRequest();
}
//Validate that the race ID exists
var race = _context.Races.Where(p => p.OwnerID == _userManager.GetUserId(User)).FirstOrDefault(t => t.ID == item.RaceId);
if (race == null)
{
return BadRequest("The RaceID you have sent does not exist");
}
var timing = _context.Timings .Where(t => t.OwnerId == _userManager.GetUserId(User))
.FirstOrDefault(t => t.Id == id);
if (timing == null)
{
return NotFound();
}
timing.RaceId = item.RaceId;
timing.StartTime = item.StartTime;
timing.EndTime = item.EndTime;
timing.Position = item.Position;
timing.OwnerId = _userManager.GetUserId(User);
timing.LastUpdated = DateTime.Now;
_context.Timings.Update(timing);
_context.SaveChanges();
return new NoContentResult();
}
/// <summary>
/// Delete a Race Timing
/// </summary>
/// <remarks>
/// Deletes a Race Timing. Note: This will orphan any related result data and is not recommended!
/// </remarks>
/// <param name="id">Race Entry ID</param>
/// <returns></returns>
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var timing = _context.Timings .Where(t => t.OwnerId == _userManager.GetUserId(User))
.FirstOrDefault(t => t.Id == id);
if (timing == null)
{
return NotFound();
}
_context.Timings.Remove(timing);
_context.SaveChanges();
return new NoContentResult();
}
/// <summary>
/// Delete all timings for a Race
/// </summary>
/// <remarks>
/// Deletes all timings for the race passed
/// </remarks>
/// <param name="raceid">Race ID</param>
/// <returns></returns>
[HttpDelete("Race/{raceid}")]
public IActionResult DeleteAllTimingsForRace(long raceid)
{
var race = _context.Races.Where(t => t.OwnerID == _userManager.GetUserId(User))
.FirstOrDefault(t => t.ID == raceid);
if (race == null)
{
return NotFound();
}
if ((race.ResultStatus == "Published") || (race.ResultStatus == "Provisional"))
{
return BadRequest("You cannot delete scans for a race which is already published");
}
foreach (var timing in _context.Timings.Where(p => p.OwnerId == _userManager.GetUserId(User))
.Where(p => p.RaceId == raceid))
{
_context.Timings.Remove(timing);
}
_context.SaveChanges();
return new NoContentResult();
}
}
}
This actually ended up being a server configuration relating to WebDAV.
I ended up testing on 3 local environments with no issues so isolated it to the server, then did some more forensic searching narrowed it down to disabling WebDAV in my Web.Config on the dpeloyed server.
<modules runAllManagedModulesForAllRequests="false">
<remove name="WebDAVModule" />
</modules>

JWT Authorization Redirects to Login

I have developed a Core 2 MVC app with Individual User Accounts using standard Identity. The MVC app is all working fine.
I am trying to add a public API and authorize requests using JWT.
However, something is going wrong when it is trying to authorize the user. When I submit a request to the controller, it redirects me to my Login page, so the response body is HTML.
I need to to authorize and return data (or not if the token is invalid).
What have I done wrong? The token generation is all OK.
Token Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using TechsportiseOnline.Models.AccountViewModels;
using Microsoft.AspNetCore.Identity;
using TechsportiseOnline.Models;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using TechsportiseOnline.Helpers;
namespace TechsportiseOnline.Controllers
{
[Produces("application/json")]
[Route("api/Token")]
public class TokenController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IConfiguration _configuration;
private readonly IOptions<JWTSettings> _jwtConfig;
public TokenController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IConfiguration configuration,
IOptions<JWTSettings> jwtConfig)
{
_userManager = userManager;
_signInManager = signInManager;
_configuration = configuration;
_jwtConfig = jwtConfig;
}
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> GenerateToken([FromBody] LoginViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(model.Email);
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (result.Succeeded)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfig.Value.SecretKey.ToString()));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_jwtConfig.Value.Issuer.ToString(),
_jwtConfig.Value.Audience.ToString(),
claims,
expires: DateTime.Now.AddDays(30),
signingCredentials: creds);
return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
}
}
}
return BadRequest("Could not create token");
}
}
}
Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TechsportiseOnline.Data;
using TechsportiseOnline.Models;
using TechsportiseOnline.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using TechsportiseOnline.Authorization;
using TechsportiseOnline.Helpers;
using Swashbuckle.AspNetCore.Swagger;
using System.IO;
using Microsoft.Extensions.PlatformAbstractions;
using static TechsportiseOnline.Helpers.Swagger;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace TechsportiseOnline
{
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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("TechsportiseDB")));
//options.UseInMemoryDatabase("Teschsportise"));
services.Configure<JWTSettings>(Configuration);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(cfg =>
{
cfg.IncludeErrorDetails = true;
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
var secretKey = Configuration.GetSection("JWTSettings.SecretKey").Value;
var issuer = Configuration.GetSection("JWTSettings.Issuer").Value;
var audience = Configuration.GetSection("JWTSettings.Audience").Value;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = Configuration.GetSection("JWTSettings.Issuer").Value,
ValidateAudience = true,
ValidAudience = Configuration.GetSection("JWTSettings.Audience").Value,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWTSettings:SecretKey"]))
};
});
services.Configure<JWTSettings>(Configuration.GetSection("JWTSettings"));
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 2;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
services.Configure<AuthMessageSenderOptions>(Configuration);
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.Cookie.Expiration = TimeSpan.FromDays(150);
options.LoginPath = "/Account/Login"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login
options.LogoutPath = "/Account/Logout"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout
options.AccessDeniedPath = "/Account/AccessDenied"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /Account/AccessDenied
options.SlidingExpiration = true;
});
// Add application services.
services.AddTransient<IEmailSender, Email>();
//services.AddTransient<ICreateContact>();
//services.AddTransient<IUpdateContact>();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Techsportise API", Version = "v1" });
c.OperationFilter<AddRequiredHeaderParameter>();
var filePath = Path.Combine(PlatformServices.Default.Application.ApplicationBasePath, "Techsportise.xml");
c.IncludeXmlComments(filePath);
});
services.AddMvc();
var skipSSL = Configuration.GetValue<bool>("LocalTest:skipSSL");
// requires using Microsoft.AspNetCore.Mvc;
services.Configure<MvcOptions>(options =>
{
// Set LocalTest:skipSSL to true to skip SSL requrement in
// debug mode. This is useful when not using Visual Studio.
if (!skipSSL)
{
options.Filters.Add(new RequireHttpsAttribute());
}
});
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddScoped<IAuthorizationHandler,
OwnerRaceAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
AdminRaceAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler,
OwnerRaceEntriesAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
AdminRaceEntriesAuthorizationHandler>();
}
// 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.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Techsportise API V1");
});
}
}
}
RacesController
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TechsportiseOnline.Data;
using TechsportiseOnline.Helpers;
using TechsportiseOnline.Models;
namespace TechsportiseOnline.Controllers
{
/// <summary>
/// This class is used as an API for Races
/// </summary>
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
public class RaceController : Controller
{
private readonly ApplicationDbContext _context;
private readonly IAuthorizationService _authorizationService;
private readonly UserManager<ApplicationUser> _userManager;
public RaceController(ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager)
{
_context = context;
_userManager = userManager;
_authorizationService = authorizationService;
}
/// <summary>
/// Get all Races
/// </summary>
/// <remarks>
/// Gets all Races which have been created by the user
/// </remarks>
/// <returns>All created Races</returns>
[HttpGet]
public IEnumerable<Race> GetAll()
{
//Get only records where the OwnerId is not the logged in User.
return _context.Races.Where(p => p.OwnerID == _userManager.GetUserId(User)).ToList();
}
/// <summary>
/// Get a single Race
/// </summary>
/// <remarks>
/// Gets the details from a single Race from it's ID
/// </remarks>
/// <param name="id">Race ID</param>
/// <returns>Single Race</returns>
[HttpGet("{id}", Name = "GetRace")]
public IActionResult GetById(long id)
{
//Only return the data when it is owned by the same Id
var item = _context.Races.Where(p => p.OwnerID == _userManager.GetUserId(User)).FirstOrDefault(t => t.ID == id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
/// <summary>
/// Get all entries for a Race
/// </summary>
/// <remarks>
/// Gets the all the entries from the race ID
/// </remarks>
/// <param name="id">Race ID</param>
/// <returns>All Entries from the given Race ID</returns>
[HttpGet("{id}/entries", Name = "GetEntriesByRaceID")]
public IEnumerable<RaceEntry> GetAllEntries(long id)
{
//Only return the data when it is owned by the same Id
//Get only records where the OwnerId is not the logged in User.
return _context.RaceEntries.Where(p => p.OwnerID == _userManager.GetUserId(User))
.Where(p => p.RaceID == id)
.ToList();
}
///// <summary>
///// Get all timings for a Race
///// </summary>
///// <remarks>
///// Gets the all the timings from the race ID
///// </remarks>
///// <param name="id">Race ID</param>
///// <returns>All timings from the given Race ID</returns>
//[HttpGet("{id}/timings", Name = "GetTimingsByRaceID")]
//public IEnumerable<Timing> GetAllTimings(long id)
//{
// //Only return the data when it is owned by the same Id
// //Get only records where the OwnerId is not the logged in User.
// return _context.Timings.Where(p => p.OwnerId == User.GetUserId())
// .Where(p => p.RaceId == id)
// .ToList();
//}
///// <summary>
///// Get the results for a Race
///// </summary>
///// <remarks>
///// Gets the all the results from the race ID
///// </remarks>
///// <param name="id">Race ID</param>
///// <returns>All results from the given Race ID</returns>
//[HttpGet("{id}/results", Name = "GetResultsByRaceID")]
//public IEnumerable<Results> GetAllResults(long id)
//{
// List<Results> raceresults = new List<Results>();
// var raceid = id;
// foreach (var raceentry in _context.RaceEntries.Where(p => p.OwnerId == User.GetUserId())
// .Where(p => p.RaceID == id))
// {
// var raceresult = new Results();
// var racedetails = _context.Races.Where(t => t.OwnerId == User.GetUserId())
// .FirstOrDefault(t => t.Id == raceid);
// var timingdetails = _context.Timings.Where(t => t.OwnerId == User.GetUserId())
// .FirstOrDefault(t => t.BibNumber == raceentry.BibNumber);
// var race = _context.Races.Where(t => t.OwnerId == User.GetUserId())
// .FirstOrDefault(t => t.Id == id);
// raceresult.AthleteUserID = raceentry.AthleteUserId;
// raceresult.Category = "Category";
// raceresult.CategoryPosition = 1;
// raceresult.ChipTime = DateTime.Now; //timingdetails.EndTime - timingdetails.StartTime;
// raceresult.Club = raceentry.Club;
// raceresult.ClubPosition = 1;
// raceresult.EntryId = raceentry.Id;
// raceresult.FirstName = raceentry.FirstName;
// raceresult.Gender = raceentry.Gender;
// raceresult.GenderPosition = 1;
// raceresult.GunTime = DateTime.Now; //race.RaceStartTime - timingdetails.EndTime;
// raceresult.LastName = raceentry.LastName;
// raceresult.OverallPosition = 0;
// raceresult.RaceDate = race.RaceDate;
// raceresult.RaceID = raceid;
// raceresult.RaceName = race.Name;
// raceresult.ResultId = 1;
// raceresult.Team = raceentry.Team;
// raceresult.TeamPosition = 1;
// raceresults.Add(raceresult);
// //build result object
// }
// //Only return the data when it is owned by the same Id
// //Get only records where the OwnerId is not the logged in User.
// return raceresults.ToList();
//}
///// <summary>
///// Publish the results of a Race
///// </summary>
///// <remarks>
///// Publishes the results as Provisional or Final. Final will submit them to RunBritain/PO10
///// </remarks>
///// <returns>The JSON for the created Race</returns>
//[HttpPost("{id}/publish", Name = "PublishResults")]
//public IActionResult Publish([FromBody] Race item)
//{
// if (item == null)
// {
// return BadRequest();
// }
// _context.Races.Add(item);
// //Set Owner ID
// item.OwnerId = User.GetUserId();
// _context.SaveChanges();
// return CreatedAtRoute("GetRace", new { id = item.Id }, item);
//}
/// <summary>
/// Creates a Race
/// </summary>
/// <remarks>
/// Creates a Race which can have entrants and timings assigned to it.
/// </remarks>
[HttpPost]
public IActionResult Create([FromBody] RacePost item)
{
if (item == null)
{
return BadRequest();
}
if (item.Name == null)
{
return BadRequest("The Race must have a Name");
}
var raceitem = new Race
{
CurrentEntries = item.CurrentEntries,
Description = item.Description,
MaxEntries = item.MaxEntries,
Name = item.Name,
ContactName = item.ContactName,
ContactEmail = item.ContactEmail,
ContactNumber = item.ContactNumber,
OwnerID = _userManager.GetUserId(User),
RaceDate = item.RaceDate,
RaceStartTime = item.RaceStartTime,
IsCompleted = item.IsCompleted,
IsPublished = item.IsPublished,
IsOpenForEntries = item.IsOpenForEntries,
LastUpdated = DateTime.Now
};
_context.Races.Add(raceitem);
_context.SaveChanges();
return CreatedAtRoute("GetRace", new { id = raceitem.ID }, raceitem);
}
/// <summary>
/// Update a Race
/// </summary>
/// <remarks>
/// Update's a Race's details
/// </remarks>
/// <param name="id">Race ID</param>
/// <returns>The JSON for the updated Race</returns>
[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] Race item)
{
if (item == null)
{
return BadRequest();
}
if (item.Name == null)
{
return BadRequest("The Race must have a Name");
}
var race = _context.Races.Where(t => t.OwnerID == _userManager.GetUserId(User))
.FirstOrDefault(t => t.ID == id);
//var race = _context.Races.FirstOrDefault(t => t.ID == id);
if (race == null)
{
return NotFound();
}
race.OwnerID = _userManager.GetUserId(User);
race.Name = item.Name;
race.ContactName = item.ContactName;
race.ContactEmail = item.ContactEmail;
race.ContactNumber = item.ContactNumber;
race.RaceDate = item.RaceDate;
race.RaceStartTime = item.RaceStartTime;
race.Description = item.Description;
race.MaxEntries = item.MaxEntries;
race.CurrentEntries = item.CurrentEntries;
race.IsCompleted = item.IsCompleted;
race.IsPublished = item.IsPublished;
race.IsOpenForEntries = item.IsOpenForEntries;
race.LastUpdated = DateTime.Now;
_context.Races.Update(race);
_context.SaveChanges();
return new NoContentResult();
}
/// <summary>
/// Delete a Race
/// </summary>
/// <remarks>
/// Deletes a Race. Note: This will orphan any related result data and is not recommended!
/// </remarks>
/// <param name="id">Race ID</param>
/// <returns></returns>
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var race = _context.Races.Where(p => p.OwnerID == _userManager.GetUserId(User)).FirstOrDefault(t => t.ID == id);
//var race = _context.Races.FirstOrDefault(t => t.Id == id);
if (race == null)
{
return NotFound();
}
var raceid = race.ID;
////Delete associated race entries
//foreach (var raceentry in _context.RaceEntries.Where(p => p.OwnerId == User.GetUserId())
// .Where(p => p.RaceID == raceid))
//{
// _context.RaceEntries.Remove(raceentry);
//}
////Delete associated race timings
//foreach (var timing in _context.Timings.Where(p => p.OwnerId == User.GetUserId())
// .Where(p => p.RaceId == raceid))
//{
// _context.Timings.Remove(timing);
//}
//Delete/Save the deletion of the race
_context.SaveChanges();
return new NoContentResult();
}
}
}
I had the same issue and on any Authorize request, i was getting redirected to /account/login. I found the solution to add the Schemes for authentication.
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpPost]
public async Task<IActionResult> Like([FromBody]int contentId)
{
var userId = await UserId();
return Json(_content.IsLiked(contentId, true, userId));
}
The same code does not work without the
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
As Shawn Wildermuth says in his blog:
Note that we’re specifying which schemes to use. The Cookies and JwtBearer both have default scheme names so unless we’re renamed the scheme (which we could do in Startup.cs), we can just use the scheme name to tell the API to use JWT only, not cookies at all.
If we try again after this, it works with a JWT Token only. If you
did want to support both (but don’t), the property
AuthenticationSchemes takes a comma delimited list of scheme names.
So, you need to specify on :
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
both of the schemes:
[Authorize(AuthenticationSchemes = "Identity.Application,"+JwtBearerDefaults.AuthenticationScheme)]
Hope this can help you.
A solution with AuthenticationSchemes worked for me.
In my case, in order to use just [Authorize], I had to change the order in my services calls.
The following order worked for me:
services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
...
}).AddEntityFrameworkStores<SqliteDbContext>().AddDefaultTokenProviders();
// Need to be called after services.AddIdentity not before (it was my mistake)
services.AddAuthentication(options =>
{
// Here is what makes [Authorize] using the JWT instead of Account/Login redirection
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
...
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
...
};
});
If you're looking to use both Identity and Jwt, try the following:
[Authorize(AuthenticationSchemes = "Identity.Application," + JwtBearerDefaults.AuthenticationScheme)]
[Produces("application/json")]
[Route("api/Test")]
public class TestController : Controller
{
[HttpGet]
public async Task<IActionResult> Get()
{
return Ok("Worked");
}
}

Categories

Resources