How to catch Unauthorized 401 ASP.NET Core web api .NET 6 - asp.net-core-webapi

I have an ASP.NET Core Web API and I'm using an exception middleware in order to catch my custom exceptions and return appropriate responses to the client.
However, in the case of "Unauthorized Access" thrown by the controller itself when using the [Authorize] attribute, how can I catch the error in my middleware?
Exception middleware
public class ExceptionMiddleware
{
readonly RequestDelegate _request;
public ExceptionMiddleware(RequestDelegate request)
{
_request = request;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _request(context);
}
catch (ValidationException ex)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
var error = ex.ToValidationExceptionDetails();
await context.Response.WriteAsJsonAsync(error);
}
catch (Exception ex) when (ex.GetErrorCode() == (int)HttpStatusCode.Conflict)
{
context.Response.StatusCode = (int)HttpStatusCode.Conflict;
var error = ex.ToConflictExceptionDetails();
await context.Response.WriteAsJsonAsync(error);
}
catch (Exception ex) when (ex.GetErrorCode() == (int)HttpStatusCode.NotFound)
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
var error = ex.ToNotFoundExceptionDetails();
await context.Response.WriteAsJsonAsync(error);
}
catch(Exception ex) when (ex.GetErrorCode() == (int)HttpStatusCode.Unauthorized)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
var error = ex.ToUnauthorizedRequestExceptionDetails();
await context.Response.WriteAsJsonAsync(error);
}
catch (Exception)
{
throw;
}
}
}
Custom exception example:
public class PostNotFoundException : Exception
{
public int ErrorCode { get; } = (int)System.Net.HttpStatusCode.NotFound;
readonly static string _defaultErrorMessage = "Post Not Found.";
public PostNotFoundException() : base(_defaultErrorMessage) { }
public PostNotFoundException(string message) : base(message) { }
public PostNotFoundException(string message, Exception innerException)
: base(message, innerException) { }
}
Controller method code:
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class PostsController : ControllerBase
{
readonly IPostService _postService;
public PostsController(IPostService postService)
{
_postService = postService;
}
[HttpGet("{id}")]
public async Task<ActionResult<PostQueryDTO>> GetPostById(Guid id)
{
return await _postService.GetPostByIdAsync(id);
}
[HttpGet("user")]
public async Task<ActionResult<IEnumerable<PostsByUserDTO>>> GetPostsByUserID()
{
return Ok(await _postService.GetPostsByUserIDAsync(HttpContext.GetUserID()));
}
[HttpGet("user/followings")]
public async Task<ActionResult<IEnumerable<PostQueryDTO>>> GetPostsByUserFollowings()
{
return Ok(await _postService.GetPostsByUserFollowingsAsync(HttpContext.GetUserID()));
}
[HttpPost]
public async Task<ActionResult<PostPOSTCommandDTO>> PostPost(PostPOSTCommandDTO post)
{
PostQueryDTO newpost = await _postService.CreatePostAsync(post, HttpContext.GetUserID());
return CreatedAtAction("GetPostById", new { id = newpost.Id }, newpost);
}
[HttpPut("{postID}")]
public async Task<IActionResult> PutPost(PostPOSTCommandDTO post, Guid postID)
{
await _postService.EditPostAsync(post, postID, HttpContext.GetUserID());
return NoContent();
}
[HttpDelete("{postID}")]
public async Task<IActionResult> DeletePost(Guid postID)
{
await _postService.DeletePostAsync(postID, HttpContext.GetUserID());
return NoContent();
}
}
So basically if an unauthorized user tries to add a post, the controller returns and 401 response, because of the Authorize attribute.
Is there a way to stop it from doing that and let me return custom response instead?

I think you may take the JwtBearerEvents into considratio
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "mytest.com",
ValidAudience = "mytest.com",
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String("base64string"))
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = async (context) =>
{
//do something here
},
OnChallenge = async (context) =>
{
//do something here
}
};
});

Related

Setting authorization header of Httpclient return internal server error 500 repone why?

I have a problem I don't know where , after setting authorization header of HttpClient return internal server error 500.
Auth Controller Login Action
namespace MagicVilla_Web.Controllers
{
public class AuthController : Controller
{
private readonly IAuthService _authService;
public AuthController(IAuthService authService)
{
_authService = authService;
}
[HttpGet]
public IActionResult Login()
{
LoginRequestDTO loginRequestDTO = new LoginRequestDTO();
return View(loginRequestDTO);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginRequestDTO loginRequestDTO)
{
APIResponse response = await _authService.LoginAsync<APIResponse>(loginRequestDTO);
if (response != null && response.IsSuccess)
{
LoginResponseDTO model = JsonConvert.DeserializeObject<LoginResponseDTO (Convert.ToString(response.Result));
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, model.User.Name));
identity.AddClaim(new Claim(ClaimTypes.Role, model.User.Role));
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
HttpContext.Session.SetString(SD.SessionToken, model.Token);
return RedirectToAction("Index","Home");
}
else
{
ModelState.AddModelError("CustomError", response.ErrorMessages.FirstOrDefault());
return View(loginRequestDTO);
}
}
[HttpGet]
public IActionResult Register()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterationRequestDTO registerationRequestDTO)
{
APIResponse result = await _authService.RegisterAsync<APIResponse>(registerationRequestDTO);
if (result != null && result.IsSuccess)
{
return RedirectToAction("Login");
}
return View();
}
[HttpGet]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync();
HttpContext.Session.SetString(SD.SessionToken, "");
return RedirectToAction("Index", "Home");
}
[HttpGet]
public async Task<IActionResult> AccessDenied()
{
return View();
}
}
}
Home Controller Index
namespace MagicVilla_Web.Controllers
{
public class HomeController : Controller
{
private readonly IVillaService villaService;
private readonly IMapper mapper;
public HomeController(IVillaService villaService, IMapper mapper)
{
this.villaService = villaService;
this.mapper = mapper;
}
public async Task<IActionResult> Index()
{
List<VillaDTO> list = new();
var response = await villaService.GetAllAsync<APIResponse>(HttpContext.Session.GetString(SD.SessionToken));
if (response != null && response.IsSuccess)
{
list = JsonConvert.DeserializeObject<List<VillaDTO>>(Convert.ToString(response.Result));
}
return View(list);
}
}
}
GetAllAsync From VillaService.cs
public class VillaService : BaseService , IVillaService
{
private readonly IHttpClientFactory clientFactory;
private string villaUrl;
public VillaService(IHttpClientFactory clientFactory, IConfiguration configuration) : base(clientFactory)
{
this.clientFactory = clientFactory;
this.villaUrl = configuration.GetValue<string>("ServiceUrls:VillaAPI");
}
public Task<T> GetAllAsync<T>(string token)
{
return SendAsync<T>(new APIRequest
{
ApiType = SD.ApiType.GET,
Url = this.villaUrl + "/api/VillaAPI",
Token = token
});
}
}
Send Async From BaseService.cs
Here response return internal server error 500 after pass the token in the header Authrization & not going to API endpoint .
namespace MagicVilla_Web.Services
{
public class BaseService : IBaseServices
{
public APIResponse APIResponse { get; set; }
public IHttpClientFactory httpClient { get; set; }
public BaseService(IHttpClientFactory httpClient)
{
this.APIResponse = new();
this.httpClient = httpClient;
}
public async Task<T> SendAsync<T>(APIRequest apiRequest)
{
try
{
var client = httpClient.CreateClient("MagicAPI");
HttpRequestMessage request = new HttpRequestMessage();
request.Headers.Add("Accept", "application/json");
request.RequestUri = new Uri(apiRequest.Url);
if (apiRequest.Data != null)
{
request.Content = new StringContent(JsonConvert.SerializeObject(apiRequest.Data), Encoding.UTF8, "application/json");
}
switch (apiRequest.ApiType)
{
case SD.ApiType.POST:
request.Method = HttpMethod.Post;
break;
case SD.ApiType.PUT:
request.Method = HttpMethod.Put;
break;
case SD.ApiType.DELETE:
request.Method = HttpMethod.Delete;
break;
default:
request.Method = HttpMethod.Get;
break;
}
HttpResponseMessage response = null;
if (!string.IsNullOrEmpty(apiRequest.Token))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiRequest.Token);
}
response = await client.SendAsync(request);
var apiContent = await response.Content.ReadAsStringAsync();
try
{
APIResponse apiResponse = JsonConvert.DeserializeObject<APIResponse>(apiContent);
if ((response.StatusCode == System.Net.HttpStatusCode.BadRequest || response.StatusCode == System.Net.HttpStatusCode.NotFound))
{
apiResponse.StatusCode = System.Net.HttpStatusCode.BadRequest;
apiResponse.IsSuccess = false;
var apiResponseSerialized = JsonConvert.SerializeObject(apiResponse);
var apiResponseDeserialized = JsonConvert.DeserializeObject<T>(apiResponseSerialized);
return apiResponseDeserialized;
}
}
catch (Exception e)
{
var exceptionResponse = JsonConvert.DeserializeObject<T>(apiContent);
return exceptionResponse;
}
var APIResponse = JsonConvert.DeserializeObject<T>(apiContent);
return APIResponse;
}
catch (Exception e)
{
var dto = new APIResponse
{
ErrorMessages = new List<string> { Convert.ToString(e.Message) },
IsSuccess = false
};
var res = JsonConvert.SerializeObject(dto);
var apiResponse = JsonConvert.DeserializeObject<T>(res);
return apiResponse;
}
}
}
}
VillaAPI Controller
[Route("api/[controller]")]
[ApiController]
public class VillaAPIController : ControllerBase
{
public readonly IVillaRepository villaRepository;
public readonly IMapper mapper;
protected APIResponse response;
public VillaAPIController(IVillaRepository villaRepository, IMapper mapper)
{
this.villaRepository = villaRepository;
this.mapper = mapper;
this.response = new();
}
[HttpGet]
[Authorize]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<IEnumerable<APIResponse>>> GetVillas()
{
try
{
IEnumerable<Villa> villaList = await villaRepository.GetAllAsync();
response.Result = mapper.Map<List<VillaDTO>>(villaList);
response.StatusCode = System.Net.HttpStatusCode.OK;
}
catch (Exception ex)
{
response.IsSuccess = false;
response.ErrorMessages = new List<string> { ex.Message };
}
return Ok(response);
}
}
Program.cs API
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddDbContext<ApplicationDbContext>(o => {
o.UseSqlServer(builder.Configuration.GetConnectionString("DefaultSQLConnection"));
});
builder.Services.AddAutoMapper(typeof(MappingConfig));
builder.Services.AddScoped<IVillaRepository, VillaRepository>();
builder.Services.AddScoped<IVillaNumberRepository, VillaNumberRepository>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddControllers(o => {
//o.ReturnHttpNotAcceptable = true;
}).AddNewtonsoftJson().AddXmlDataContractSerializerFormatters();
builder.Services.AddEndpointsApiExplorer();
//get secret key from appSittings.json
var key = builder.Configuration.GetValue<string>("ApiSettings:Secret");
//add Authentication Configurations
builder.Services.AddAuthentication(x =>
{
//configure the default authentication scheme and the default challenge scheme. Both of them we want to use JWT bearer defaults
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
//we have something called as add JWT bearer and then we can configure options on that.
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false; //we have the required https metadata
x.SaveToken = true; //save the token
x.Authority = "https://localhost:7003/";
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
};
});
//to enable bearer in swagger
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description =
"JWT Authorization header using the Bearer scheme. \r\n\r\n " +
"Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\n" +
"Example: \"Bearer 12345abcdef\"",
Name = "Authorization",
In = ParameterLocation.Header,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header
},
new List<string>()
}
});
});
//app => configured application
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Program.cs Web
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddAutoMapper(typeof(MappingConfig));
builder.Services.AddHttpClient<IVillaService, VillaService>();
builder.Services.AddScoped<IVillaService, VillaService>();
builder.Services.AddHttpClient<IVillaNumberService, VillaNumberService>();
builder.Services.AddScoped<IVillaNumberService, VillaNumberService>();
builder.Services.AddHttpClient<IAuthService, AuthService>();
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddDistributedMemoryCache();
IdentityModelEventSource.ShowPII = true;
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.SlidingExpiration = true;
options.LoginPath = "/Auth/Login";
options.AccessDeniedPath = "/Auth/AccessDenied";
});
//add authorization
//builder.Services.AddAuthorization();
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(100);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
If I turn off the authorization and not send the token in the header, everything works fine .MagicVilla_VillaAPI this project link in Github :https://github.com/MoZytoon/MagicVilla
The problem was with the program.cs file for the Api. here :
x. Authority = "https://localhost:7003/";
When setting the the AddAuthentication .

How to create a custom error message for Authorize attribute?

I want only admins to have access to this controller and its actions, so I've written this code:
[Authorize(Roles = Helper.AdminRole)]
public class AdminController : Controller
{
public IActionResult AdminPanel()
{
return View();
}
//other actions only available to admins
}
If the user is not logged in and he's not in the specified role I get a 404 Not Found page and this in the URL:
..../AccessDenied?ReturnUrl=%2FAdmin%2FAdminPanel
How can I make a custom error page for this scenario where the user is asked to log in so he can confirm his role, and when he does log in successfully AND he is in the right role to be redirected to where he wanted to go, but if his role is invalid to be redirected elsewhere/ shown a custom error page?
Your error was caused due to lack of Loginpath settings,not wrong role or password.(So the error code was 404 not 401)
You could see the test Result:
If you want to custom error page,you could read the official document:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-5.0
I tried with the codes below:
ErrorResult class:
public class ErrorResult
{
public bool Success { get; set; } = true;
public string Msg { get; set; } = "";
public string Type { get; set; } = "";
public object Data { get; set; } = "";
public object DataExt { get; set; } = "";
}
ErrorHandlingMiddleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
var statusCode = context.Response.StatusCode;
if (ex is ArgumentException)
{
statusCode = 200;
}
await HandleExceptionAsync(context, statusCode, ex.Message);
}
finally
{
var statusCode = context.Response.StatusCode;
var msg = "";
if (statusCode == 401)
{
msg = "unauthorize";
}
else if (statusCode == 404)
{
msg = "NotFound";
}
else if (statusCode == 400)
{
msg = "BadRequest";
}
else if (statusCode != 200)
{
msg = "Unkonwn";
}
if (!string.IsNullOrWhiteSpace(msg))
{
await HandleExceptionAsync(context, statusCode, msg);
}
}
}
private static Task HandleExceptionAsync(HttpContext context, int statusCode, string msg)
{
var result = JsonConvert.SerializeObject(new ErrorResult() { Success = false, Msg = msg, Type = statusCode.ToString() });
context.Response.ContentType = "application/json;charset=utf-8";
return context.Response.WriteAsync(result);
}
}
public static class ErrorHandlingExtensions
{
public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ErrorHandlingMiddleware>();
}
}
in startup class:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
app.UseErrorHandling();
....
}
The Result:
Take cookie authentication as an example, you just need to configure it like this in program.cs(.Net 6):
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x =>
{
//When user doesn't login and he access to an action with [Authorize],
//He will redirect to the loginPath
x.LoginPath = "/{controller}/{action}";
//When user has loged in but the role is not the specified role,
//He will redicet to the AccessDeniedPath,
//Then you can custom your own error page in this path.
x.AccessDeniedPath = "/{controller}/{action}";
});

signalr : after Authentication i get error 400 (Bad Request) in client-side

after Task.CompletedTask i get error 400 (Bad Request) in "client-side"
What am I missing to go through the Authentication phase?
Server:
public class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = options.DefaultScheme;
options.DefaultChallengeScheme = options.DefaultScheme;
})
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Headers["Authorization"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken))
{
context.Token = accessToken.ToString().Substring("Token ".Length).Trim();
}
return Task.CompletedTask;
}
};
});
services.AddSignalR();
services.AddSingleton<IMongoDBService, MongoDBService>();
}
public override void Configure(IApplicationBuilder builder, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
builder.UseCors("CorsPolicy");
builder.UseAuthentication();
builder.UseEndpoints(endpoints =>
{
endpoints.MapHub<PrintEventsHub>("/Events/PrintEventsHub");
endpoints.MapHub<NotificationEventsHub>("/Events/NotificationEventsHub");
});
}
}
Hub:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class NotificationEventsHub : Hub//, IPrinterEventsHub
{
private readonly ISession _session;
private readonly ITelegramServices _telegramServices;
private readonly ISaveEventToDBServices _saveEventToDBServices;
private readonly IHubContext<NotificationEventsHub> _Context;
private string ConnectionID;
public NotificationEventsHub(IHubContext<NotificationEventsHub> context,
ISession session,
ITelegramServices telegramServices,
ISaveEventToDBServices saveEventToDBServices)
{
_Context = context;
_session = session;
_telegramServices = telegramServices;
_saveEventToDBServices = saveEventToDBServices;
}
public override Task OnConnectedAsync()
{
ConnectionID = this.Context.ConnectionId;
var connectedUser = Context.User.Identity;
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
return base.OnDisconnectedAsync(exception);
}
public async Task GetNotificationMsg(string jsonStringNotification)
{
}
}
Client:
public static async Task<bool> SendPrinterEventToCloudManager()
{
var SignalRConnectionInfo = new SignalRConnectionInfo();
SignalRConnectionInfo.AccessToken = "test";
string _SignalRUrl =
"https://localhost:44300/Events/NotificationEventsHub";
try
{
HubConnection connection = new HubConnectionBuilder().WithUrl(_SignalRUrl,
o => o.AccessTokenProvider = () => Task.FromResult(SignalRConnectionInfo.AccessToken)).Build();
await connection.StartAsync();
await connection.InvokeAsync("GetNotificationMsg", "testmsg");
return true;
}
catch (Exception ex)
{
var msg = ex.Message;
return false;
}
}
i am try to use JwtBearer token and after i read the value what's next step to aprrove that the user Authentication?

Registration with email confirmation ASP .NET Core

I'm learning ASP .NET and I'd want to make a simple registration/login page.
I've created my public class AccountController : Controller in this way:
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IEmailSender _emailSender;
private readonly ILogger _logger;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ILogger<AccountController> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
_logger = logger;
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("Utente loggato.");
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User bloccato.");
return RedirectToAction(nameof(Lockout));
}
else
{
ModelState.AddModelError(string.Empty, "Tentativo di login fallito.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public IActionResult Register(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_logger.LogInformation("L'utente ha creato un nuovo account con una nuova password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
//diallowed signin for self registration, email should be confirmed first
//await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("L'utente ha creato un nuovo account con una nuova password.");
return RedirectToConfirmEmailNotification();
}
AddErrors(result);
}
return View(model);
}
public async Task<IActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return RedirectToAction(nameof(HomeController.Index), "Home");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
}
var result = await _userManager.ConfirmEmailAsync(user, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
}
Then I created Startup class and I've written the following method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Landing}/{action=Index}/{id?}");
});
}
When I register a new user I can see it into the database but I don't receive any mail and I can't log in with the new user if I try.
I've watched some tutorials on YouTube about it and read the Microsoft documentation. To me it seems correct compared with what I've done, but surely I have to modify something and I don't notice it.
EDIT: this is what I've done for EmailSender and NetcoreService class:
public class EmailSender : IEmailSender
{
private SendGridOptions _sendGridOptions { get; }
private INetcoreService _netcoreService { get; }
private SmtpOptions _smtpOptions { get; }
public EmailSender(IOptions<SendGridOptions> sendGridOptions,
INetcoreService netcoreService,
IOptions<SmtpOptions> smtpOptions)
{
_sendGridOptions = sendGridOptions.Value;
_netcoreService = netcoreService;
_smtpOptions = smtpOptions.Value;
}
public Task SendEmailAsync(string email, string subject, string message)
{
//send email using sendgrid via netcoreService
_netcoreService.SendEmailBySendGridAsync(_sendGridOptions.SendGridKey,
_sendGridOptions.FromEmail,
_sendGridOptions.FromFullName,
subject,
message,
email).Wait();
return Task.CompletedTask;
}
}
NetcoreService class:
public async Task SendEmailBySendGridAsync(string apiKey, string fromEmail, string fromFullName, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress(fromEmail, fromFullName),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email, email));
await client.SendEmailAsync(msg);
}

How to fix 'Unauthorized Error 401', even when logged in?

I'm creating this webpage in Angular with .NET as my backend. Everything was working just fine, but today (all of a sudden) I'm getting Unauthorized errors (401). When I remove my authorization, I'm getting internal server errors (meaning my 'User.Identity.Name') does not work. So I'm guessing there is a problem with my JWT
I have tried to remove authorization headers. But that gave me an internal server error (500). The only requests that work are those not using IdentityUser and thus being [AllowAnonymous]
I have no idea what the problem could be, so it's hard to show some code. But since I think the problem has something to do with my JWT I'll add some code of that.
Some controller code .NET
[Route("api/[controller]")]
[ApiController]
[Produces("application/json")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class BierenController : ControllerBase
{
private readonly IBierRepository _bierRepository;
private readonly IGebruikerRepository _gebruikerRepository;
private readonly IBrouwerRepository _brouwerRepository;
public BierenController(IBierRepository bierRepository, IGebruikerRepository gebruikerRepository, IBrouwerRepository brouwerRepository)
{
_bierRepository = bierRepository;
_gebruikerRepository = gebruikerRepository;
_brouwerRepository = brouwerRepository;
}
[HttpGet]
[AllowAnonymous]
public IEnumerable<Bier> GetBieren()
{
return _bierRepository.GetAll().OrderBy(b => b.Naam);
}
[HttpGet("{id}")]
[AllowAnonymous]
public ActionResult<Bier> GetBier(int id)
{
Bier bier = _bierRepository.GetBy(id);
if (bier == null)
return NotFound();
return bier;
}
[HttpPost]
[Authorize(Roles = "Brouwer, Admin")]
public IActionResult PostBier(Bier bier)
{
Brouwer brouwer = _brouwerRepository.GetBy(User.Identity.Name);
brouwer.AddBier(bier);
_brouwerRepository.SaveChanges();
return NoContent();
}
Startup.cs configuration
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().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<BrouwerContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("BierContext")));
services.AddScoped<IBrouwerRepository, BrouwerRepository>();
services.AddScoped<IBierRepository, BierRepository>();
services.AddScoped<IGebruikerRepository, GebruikerRepository>();
services.AddScoped<IPostRepository, PostRepository>();
services.AddOpenApiDocument(c =>
{
c.Title = "BabAPI";
c.Version = "v1";
c.Description = "De API voor BAB";
c.DocumentProcessors.Add(new SecurityDefinitionAppender("JWT Token", new SwaggerSecurityScheme
{
Type = SwaggerSecuritySchemeType.ApiKey,
Name = "Authorization",
In = SwaggerSecurityApiKeyLocation.Header,
Description = "Copy 'Bearer' + valid JWT token into field" }));
c.OperationProcessors.Add(new OperationSecurityScopeProcessor("JWT Token"));
});
services.AddCors(options =>
options.AddPolicy("AllowAllOrigins", builder =>
builder.AllowAnyOrigin()
));
services.AddDefaultIdentity<IdentityUser>(cfg => cfg.User.RequireUniqueEmail = true).AddRoles<IdentityRole>().AddEntityFrameworkStores<BrouwerContext>();
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
options.User.RequireUniqueEmail = true;
});
services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false; x.SaveToken = true; x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"])),
ValidateIssuer = false,
ValidateAudience = false,
RequireExpirationTime = true
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider services)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// 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.UseStaticFiles();
app.UseHttpsRedirection();
app.UseMvc();
CreateUserRoles(services).Wait();
app.UseAuthentication();
app.UseSwaggerUi3();
app.UseSwagger();
app.UseCors("AllowAllOrigins");
}
Angular authentication interceptor
export class AuthenticationInterceptor implements HttpInterceptor {
constructor(private authService: AuthenticationService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService.token.length) {
const clonedRequest = req.clone({
headers: req.headers.set('Authorization', `Bearer ${this.authService.token}`)
});
return next.handle(clonedRequest);
}
return next.handle(req);
}
}
.NET Login/Register/Tokencreation
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult<String>> CreateToken(LoginDTO model)
{
var user = await this._userManager.FindByNameAsync(model.GebruikersNaam);
if(user == null)
{
user = await this._userManager.FindByEmailAsync(model.GebruikersNaam);
}
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (result.Succeeded)
{
string token = await GetTokenAsync(user);
return Created("", token);
}
}
return BadRequest();
}
private async Task<string> GetTokenAsync(IdentityUser user)
{
var role = await _userManager.GetRolesAsync(user);
IdentityOptions _options = new IdentityOptions();
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName),
new Claim(_options.ClaimsIdentity.RoleClaimType, role.FirstOrDefault())
}),
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256),
Expires = DateTime.Now.AddMinutes(30)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return token;
}
[AllowAnonymous]
[HttpPost("register")]
public async Task<ActionResult<String>> Register(RegisterDTO model)
{
IdentityUser user = new IdentityUser { UserName = model.GebruikersNaam, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
if (model.Email.ToLower().Equals("admin1#hotmail.com"))
{
Brouwer brouwer = new Brouwer { Naam = model.GebruikersNaam, Email = model.Email };
_brouwerRepository.Add(brouwer);
_brouwerRepository.SaveChanges();
await _userManager.AddToRoleAsync(user, "Admin");
}
else if (model.Email.ToLower().Equals("brouwer1#hotmail.com"))
{
Brouwer brouwer = new Brouwer { Naam = model.GebruikersNaam, Email = model.Email };
_brouwerRepository.Add(brouwer);
_brouwerRepository.SaveChanges();
await _userManager.AddToRoleAsync(user, "Brouwer");
}
else
{
Gebruiker gebruiker = new Gebruiker { Email = model.Email, Gebruikersnaam = model.GebruikersNaam };
_gebruikerRepository.Add(gebruiker);
_gebruikerRepository.SaveChanges();
await _userManager.AddToRoleAsync(user, "User");
}
string token = await GetTokenAsync(user);
return Created("", token);
}
else {
return BadRequest();
}

Resources