I am working on an API with ASP.NET Core 3.1 and I have created multiple controllers. This API uses a JWT bearer token to authenticate the caller where LoginController has three endpoints Login, Error and MenuList (list of menus according to role). Only this controller works fine like the Menulist returns a http 401 error when the user is not logged in, and and when the user is logged in, it works as it should.
The issue is when I hit request controllers other then LoginController, they all return 200 but response data says code: undocumented and detail:
TypeError: Failed to fetch
I tried to debug this issue locally but the request won't even hit the controller function like I put a breakpoint on the controller to find out where the problem was but it just won't hit the breakpoint, but as soon as I add [AllowAnonymous] attribute on the controller function, it starts working and fetches data normally.
Honestly, I have no idea where this problem is originated so I'll just be giving all my code to know the issue and it's solution.
LoginController:
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BAL.Interfaces;
using ViewModel.Model;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Authorization;
using System.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Http;
using DAL.DBEntities;
using Newtonsoft.Json;
using Microsoft.Extensions.Logging;
using ViewModel.vt_Common;
namespace IndigoAPI.Controllers
{
public class LoginController : BaseController
{
#region StructureWork
private readonly ILoginRepository LoginRepository; // Login Repository Interface
public readonly UserManager<ApplicationUser> userManager; // ASP Identity
private readonly RoleManager<IdentityRole> roleManager; // ASP Identity
private readonly IConfiguration _configuration; // Configuration property
private readonly ILogger<LoginController> _logger; // Logger Dependance Injection
private readonly IExceptionRepository ExceptionRepository; // for exception log
public LoginController(ILogger<LoginController> logger, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IExceptionRepository ExceptionRepository, IConfiguration configuration, ILoginRepository LoginRepository)
{
this.LoginRepository = LoginRepository;
this.ExceptionRepository = ExceptionRepository;
this.userManager = userManager;
this.roleManager = roleManager;
_configuration = configuration;
_logger = logger;
}
#endregion
// me
/// <summary>
/// This method is created to authenticate user and return token.
/// This method uses asp authentication
/// </summary>
/// <param name="Header"></param>
/// <returns></returns>
[HttpPost]
[Route("Login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] LoginForm Header)
{
try
{
if (ModelState.IsValid)
{
var user = await userManager.FindByNameAsync(Header.UserName); // find user name
var userRoles = await userManager.GetRolesAsync(user); // get role
if (userRoles.Count > 0)
{
if (user != null && await userManager.CheckPasswordAsync(user, Header.Password))
{
if (await LoginRepository.IsUserActive(Header.UserName) != null)
{
List<MenuList> menulist = new List<MenuList>();
menulist = await LoginRepository.getMenuList_byRole(userRoles[0]); // get menu list from repo
List<UserPages> userPages = new List<UserPages>();
userPages = await LoginRepository.getPageListByRole(userRoles[0]);
UserPagesList.List = userPages;
var jsonstr = JsonConvert.SerializeObject(userPages);
TempData["UserPages"] = jsonstr;
if (menulist != null)
{
#region Claims
var hashPassword = await LoginRepository.getUserHashPassword(user.Id);
var password = vt_Common.DecryptCipherTextToPlainText(hashPassword);
#endregion
var authClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
}; // create claims
foreach (var userRole in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
} // add roles in claims
string menu = "http://foo.it/claim/menulist";
authClaims.Add(new Claim(menu, JsonConvert.SerializeObject(menulist))); // add menulist in claims
string pages = "http://foo.it/claim/pages";
authClaims.Add(new Claim(pages, JsonConvert.SerializeObject(userPages))); // add menulist in claims
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"])); // create key
var token = new JwtSecurityToken(
issuer: _configuration["JWT:ValidIssuer"],
audience: _configuration["JWT:ValidAudience"],
expires: DateTime.Now.AddHours(3),
claims: authClaims,
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
); // create token
await LoginRepository.LastLogin(Header.UserName, this.HttpContext.Connection.RemoteIpAddress.ToString());
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo,
ID = user.Id,
UserName = user.UserName,
Role = userRoles[0],
Menulist = menulist
}); // return
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = "No Menu Found." });
}
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = "User is not active." });
}
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = "Wrong user name or password." });
}
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = "Role Not Exist." });
}
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = "Fill the Required Fields." });
}
}
catch (Exception ex)
{
await ExceptionRepository.writeException(Controller(), ex.Message, CurrentUser.UserName, CurrentUser.UserIP);
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = ex.Message });
}
}
/// <summary>
/// If user requested to unauthorized method this method will hit
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("error")]
public async Task<IActionResult> Error()
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = "Unauthorized." });
}
/// <summary>
/// This method return menulist
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("MenuList")]
public async Task<IActionResult> MenuList()
{
try
{
List<MenuList> menulist = new List<MenuList>();
menulist = await LoginRepository.getMenuList_byRole(CurrentUser.RoleName); // get menu list from repo
return Json(new { status = true, data = menulist });
}
catch (Exception ex)
{
await ExceptionRepository.writeException(Controller(), ex.Message, CurrentUser.UserName, CurrentUser.UserIP);
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = ex.Message });
}
}
}
}
UserController: in this controller, GetUser won't fetch data but GetRoles will because of the AllowAnonymous annotation:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using BAL.Interfaces;
using DAL.DBEntities;
using Microsoft.Extensions.Logging;
using ViewModel.Model;
using Microsoft.AspNetCore.Http;
using System;
using ViewModel.vt_Common;
using Microsoft.AspNetCore.Authorization;
namespace IndigoAPI.Controllers
{
public class UserController : BaseController
{
#region StructureWork
private readonly IUserRepository UserRepository; // Login Repository Interface
public readonly UserManager<ApplicationUser> userManager; // ASP Identity
private readonly RoleManager<IdentityRole> roleManager; // ASP Identity
private readonly ILogger<LoginController> _logger; // Logger Dependance Injection
private readonly IExceptionRepository ExceptionRepository; // for exception log
public UserController(ILogger<LoginController> logger, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IExceptionRepository ExceptionRepository, IUserRepository UserRepository)
{
this.UserRepository = UserRepository;
this.ExceptionRepository = ExceptionRepository;
this.userManager = userManager;
this.roleManager = roleManager;
_logger = logger;
}
#endregion
/// <summary>
/// This method will return user list
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("GetUser")]
public async Task<IActionResult> GetUser()
{
try
{
List<User> roles = new List<User>();
roles = await UserRepository.getUser();
return Json(new { status = true, data = roles });
}
catch (Exception ex)
{
await ExceptionRepository.writeException(Controller(), ex.Message, CurrentUser.UserName, CurrentUser.UserIP);
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = ex.Message });
}
}
/// <summary>
/// This method will return role list
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("GetRole")]
[AllowAnonymous]
public async Task<IActionResult> GetRole()
{
try
{
List<UserRoles> roles = new List<UserRoles>();
roles = await UserRepository.getRole();
return Json(new { status = true, data = roles });
}
catch (Exception ex)
{
await ExceptionRepository.writeException(Controller(), ex.Message, CurrentUser.UserName, CurrentUser.UserIP);
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = ex.Message });
}
}
/// <summary>
/// This method will register users
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
[Route("CreateUser")]
public async Task<IActionResult> CreateUser([FromBody] RegisterModel model)
{
try
{
var userExists = await userManager.FindByNameAsync(model.UserName); //check user exist or not
if (userExists != null)
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User already exists!" });
ApplicationUser user = new ApplicationUser()
{
Email = model.UserEmail,
SecurityStamp = Guid.NewGuid().ToString(),
UserName = model.UserName
};
var result = await userManager.CreateAsync(user, model.UserPassword); //create user
if (result.Succeeded)
{
if (await roleManager.RoleExistsAsync(model.RoleName))
{
if (await roleManager.RoleExistsAsync(model.RoleName))
{
await userManager.AddToRoleAsync(user, model.RoleName); //link role and user
model.UserPassword = vt_Common.EncryptPlainTextToCipherText(model.UserPassword);
userExists = await userManager.FindByNameAsync(model.UserName);
await UserRepository.createUpdateUser(model, userExists.Id, CurrentUser.UserName, CurrentUser.UserIP); //add user
//await UserRepository.createUpdateUser(model, userExists.Id, "aliyan", "172.16.200.235"); //add user
}
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = "Role Not Exist" });
}
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = result.ToString() });
}
return Ok(new Response { Status = "Success", Message = "User created successfully!" });
}
catch (Exception ex)
{
await ExceptionRepository.writeException(Controller(), ex.Message, CurrentUser.UserName, CurrentUser.UserIP);
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = ex.Message });
}
}
/// <summary>
/// this method will update user
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPut]
[Route("UpdateUser")]
public async Task<IActionResult> UpdateUser([FromBody] RegisterModel model)
{
try
{
var olduserExists = await userManager.FindByIdAsync(model.ASPUserID);
var newUserExist = await userManager.FindByNameAsync(model.UserName);
if (olduserExists != null)
{
if (newUserExist == null || olduserExists == newUserExist)
{
model.UserPassword = "";
await UserRepository.createUpdateUser(model, model.ASPUserID, CurrentUser.UserName, CurrentUser.UserIP); //add user
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = "User already exist" });
}
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = "User not exist" });
}
return Ok(new Response { Status = "Success", Message = "User updated successfully!" });
}
catch (Exception ex)
{
await ExceptionRepository.writeException(Controller(), ex.Message, CurrentUser.UserName, CurrentUser.UserIP);
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "false", Message = ex.Message });
}
}
}
}
Startup.cs:
using DAL.DBEntities;
using IndigoAPI.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System;
using System.Text;
using System.Threading.Tasks;
namespace IndigoAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddControllersWithViews();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "IndigoAPI", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please enter JWT token in the field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
services.AddDbContext<IndigoDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("IndigoDB")));
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredLength = 6;
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<IndigoDBContext>()
.AddDefaultTokenProviders();
services.AddCors(options =>
{
options.AddPolicy("AllowAll", builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = Configuration["JWT:ValidAudience"],
ValidIssuer = Configuration["JWT:ValidIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]))
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Console.WriteLine("Authentication failed: " + context.Exception.Message);
return Task.CompletedTask;
},
OnChallenge = context =>
{
Console.WriteLine("Challenge error: " + context.Error + " - " + context.ErrorDescription);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Console.WriteLine("Token validated successfully");
return Task.CompletedTask;
}
};
});
services.AddAuthorization();
services.RegisterServices(Configuration);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "IndigoAPI v1"));
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseCors("AllowAll");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
BaseController:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using ViewModel.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication;
using System.Data;
using ViewModel.vt_Common;
using System.Collections.Generic;
using System.Linq;
using System;
using DAL.Repositories;
namespace IndigoAPI.Controllers
{
[ApiController]
[Route("api/[controller]"), Authorize]
public class BaseController : Controller
{
private string Token;
private UserProfile User;
public UserProfile CurrentUser
{
get
{
User = new UserProfile();
User.UserName = vt_Common.getUserNameFromToken(Token);
User.RoleName = vt_Common.getUserRoleFromToken(Token);
User.UserIP = this.HttpContext.Connection.RemoteIpAddress.ToString();
User.MenuList = vt_Common.getMenuListDataTableFromJson(Token);
User.UserPages = vt_Common.getUserPagesDataTableFromJson(Token);
return User;
}
set
{
User = value;
}
}
protected string Controller()
{
return Convert.ToString(TempData["ControllerName"]);
}
protected string GetToken()
{
return Token;
}
public async override void OnActionExecuting(ActionExecutingContext filterContext)
{
try
{
var token = HttpContext.GetTokenAsync("access_token");
if (token.Result != null)
{
Token = token.Result.ToString();
var currentController = filterContext.RouteData.Values["controller"].ToString();
var currentAction = filterContext.RouteData.Values["action"].ToString();
TempData["ControllerName"] = currentController + "/" + currentAction;
string Page_URL = string.Empty;
Page_URL = currentController + "/" + currentAction;
if (Page_URL.ToLower() != "login/menulist")
{
List<UserPages> pages = new List<UserPages>();
pages = vt_Common.convertJsonToPageURLList(Convert.ToString(TempData["UserPages"]));
LoginRepository login = new LoginRepository();
pages = await login.getPageListByRole(CurrentUser.RoleName);
pages = UserPagesList.List;
var aa = Convert.ToString(TempData["UserPages"]);
var right = pages.Where(x => x.PageURL.ToLower() == Page_URL.ToLower()).FirstOrDefault();
if (right == null)
{
filterContext.Result = new RedirectResult("~/api/login/error");
}
}
}
}
catch (System.Exception e)
{
filterContext.Result = new RedirectResult("~/api/login/error");
}
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
{
//context.HttpContext.Trace.Write("Exception thrown");
}
}
}
}