I am incorporating AWS-CognitoIdentity Provider in my ASP.Net Core Web Api project and after following the official documentation i still get HttpContext.User NULL. Is there is a step by step guide that someone has used before successfully to get AWS-CognitoIdentity provider working.
I have setup the CognitoIdentity in my Startup.cs and later in my other controllers i am trying to access the User.
public class Startup
{
private static readonly ILog logger = LogManager.GetLogger(typeof(Startup));
private string poolId;
private string appClientId;
private static string providerName;
private static AmazonCognitoIdentityProviderClient provider;
private static CognitoUserPool pool;
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
appClientId = Configuration.GetValue<string>("AWS:UserPoolClientId");
providerName = Configuration.GetValue<string>("AWS:ProviderName");
poolId = Configuration.GetValue<string>("AWS:UserPoolId");
AWSConfigs.RegionEndpoint = RegionEndpoint.EUWest2;
provider = new AmazonCognitoIdentityProviderClient();
pool = new CognitoUserPool(poolId, appClientId, provider, "");
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<IdentityOptions>(options =>
{
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
});
services.AddCognitoIdentity();
services.AddAuthentication("Bearer").AddJwtBearer(options =>
{
options.Audience = Configuration.GetValue<string>("AWS:UserPoolClientId");
options.Authority = Configuration.GetValue<string>("AWS:ProviderName");
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration.GetValue<string>("AWS:ProviderName"),
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidAudience = Configuration.GetValue<string>("AWS:UserPoolClientId"),
ValidateAudience = true,
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
var json = new WebClient().DownloadString(Configuration.GetValue<string>("AWS:MetadataAddress"));
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
return (IEnumerable<SecurityKey>)keys;
},
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
Console.WriteLine("Message Received-------------------------------------------------------------\n");
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Console.WriteLine("TokenValidated Received-------------------------------------------------------\n");
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
UserManager<CognitoUser> _userManager,
SignInManager<CognitoUser> _signInManager)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UsePermissions();
app.UseMvc();
app.UseSwagger();
}
}
Controller
namespace DataControllers
{
//[Authorize]
[Route("api/[controller]/[action]")]
public class ContentController : Controller
{
private readonly CognitoUserManager<CognitoUser> _userManager;
public ContentController(UserManager<CognitoUser> userManager)
{
_userManager = userManager as CognitoUserManager<CognitoUser>;
}
[HttpGet]
public async Task<IActionResult> Menu()
{
var email = User.Claims.FirstOrDefault(e => e.Type == "email"); ;
}
}
}
Related
I have a api endpoints that allows me to login. Then I have a other api endpoint where I want to get data from.
Here is a exemple of data that I want to get from my postman As you can see when I use the login endpoint I get a token
Then I want to use the token to get the clients but it does not work I get A 401 here is a image of the problem. As you can see I have used the token but I am still not authorized why is that? Thank you for your help.
I have the following Controller to authenticate.
[Route("api/auth")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly ILogger<AuthController> _logger;
private readonly IEmployeeService _employeeService;
public AuthController(ILogger<AuthController> logger, IEmployeeService employeeService)
{
_logger = logger;
_employeeService = employeeService;
}
[HttpPost, Route("login")]
public IActionResult Login([FromBody] LoginModel user)
{
Employee employeeFromDB = Task.Run(async () => await _employeeService.GetByUserName(user.UserName)).Result;
if (user == null)
{
return BadRequest("Invalid client request");
}
if(employeeFromDB == null)
{
return Unauthorized();
}
else if (user.UserName == employeeFromDB.UserName && user.Password == "123qwe")
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey#45"));
var signingCreditials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokenOptions = new JwtSecurityToken(
issuer: "https://localhost:44363/",
audience: "https://localhost:44363/",
claims: new List<Claim>(),
expires: DateTime.Now.AddMinutes(5),
signingCredentials: signingCreditials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
return Ok(new { Token = tokenString });
}
return Unauthorized();
}
I have the following controller that is secured
[Route("api/[controller]")]
[ApiController]
public class ClientsController : ControllerBase
{
private readonly ILogger<ClientsController> _logger;
private readonly IClientService _clientService;
public ClientsController(ILogger<ClientsController> logger, IClientService clientService)
{
_logger = logger;
_clientService = clientService;
}
// GET api/clients
[HttpGet]
[Authorize]
public IEnumerable<ClientViewModel> Get()
{
ClientViewModel clientViewModel;
List<ClientViewModel> listClientViewModels = new List<ClientViewModel>();
var clients = Task.Run(async () => await _clientService.GetAllClients()).Result;
foreach (var client in clients)
{
clientViewModel = new ClientViewModel();
clientViewModel.ClientId = client.ClientId;
clientViewModel.Active = client.Active;
clientViewModel.Address = client.Address;
clientViewModel.City = client.City;
clientViewModel.ClienteName = client.ClienteName;
clientViewModel.ComercialEmployeeId = client.ComercialEmployeeId;
clientViewModel.Confirmed = client.Confirmed;
clientViewModel.CountryId = client.CountryId;
clientViewModel.CreationDate = client.CreationDate;
clientViewModel.DANE = client.DANE;
clientViewModel.Department = client.Department;
clientViewModel.ElectronicBillingEmail = client.ElectronicBillingEmail;
clientViewModel.Eliminated = client.Eliminated;
clientViewModel.NIT = client.NIT;
clientViewModel.PostalCode = client.PostalCode;
clientViewModel.Phone = client.Phone;
listClientViewModels.Add(clientViewModel);
}
return listClientViewModels;
}
}
}
Here is my startup code
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.AddCors(options =>
{
options.AddPolicy("EnableCORS", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(opttions =>
{
opttions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://localhost:44363",
ValidAudience = "https://localhost:44363",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey#45"))
};
});
services.AddControllersWithViews();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<DatabaseMigrator>();
services.AddDbContext<erp_colombiaDbContext>(options => options.UseMySql(
Configuration.GetConnectionString("DefaultConnection"),
optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(DesignTimeDbContextFactory).Assembly.FullName)));
services.AddDbContext<erp_colombiaDbContext>(options => options.UseMySql(
Configuration.GetConnectionString("DefaultConnection"),
optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(DesignTimeDbContextFactory).Assembly.FullName)));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseCors("EnableCORS");
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
actually am new in asp.net core 3.1 , i am trying to create user login and register with cookies
when i am try to get ClaimTypes.NameIdentifier always return null, can you help me please ?
controller code
public class AccountController : ControllerBase
{
private readonly ApiSiteDbContext _db;
private readonly UserManager<AppUser> _userManager;
private readonly SignInManager<AppUser> _signInManager;
private readonly RoleManager<AppRole> _roleManager;
public AccountController(ApiSiteDbContext db,
UserManager<AppUser> userManager,
SignInManager<AppUser> signInManager,
RoleManager<AppRole> roleManager)
{
_db = db;
_userManager = userManager;
_signInManager = signInManager;
_roleManager = roleManager;
}
[AllowAnonymous]
[HttpPost("Login")]
public async Task<IActionResult> Login(LoginModel loginModel)
{
var user = await _userManager.FindByEmailAsync(loginModel.Email);
// **** this is always return null *****
var id = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (id != null)
{
return BadRequest("User already logged !!");
}
var result = await _signInManager.PasswordSignInAsync(user, loginModel.Password, loginModel.RememberMe, true);
if (result.Succeeded)
{
if (await _roleManager.RoleExistsAsync("User"))
{
if (!await _userManager.IsInRoleAsync(user, "User"))
{
await _userManager.AddToRoleAsync(user, "User");
}
}
var roleName = await GetRoleNameByUserId(user.Id);
if (roleName != null)
{
AddCookies(user.UserName, user.Id, roleName, loginModel.RememberMe, user.Email);
}
return Ok();
}
else if (result.IsLockedOut)
{
return Unauthorized("Your account were locked");
}
return BadRequest("Wrong password!");
//return StatusCode(StatusCodes.Status204NoContent);
}
public async void AddCookies(string userName, string userId, string roleName, bool remember, string email)
{
var claim = new List<Claim>
{
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Email, email),
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Role, roleName),
};
var claimIdentity = new ClaimsIdentity(claim, CookieAuthenticationDefaults.AuthenticationScheme);
if (remember)
{
var authProperties = new AuthenticationProperties
{
AllowRefresh = true,
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddDays(10)
};
await HttpContext.SignInAsync
(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimIdentity),
authProperties
);
}
else
{
var authProperties = new AuthenticationProperties
{
AllowRefresh = true,
IsPersistent = false,
ExpiresUtc = DateTime.UtcNow.AddMinutes(30)
};
await HttpContext.SignInAsync
(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimIdentity),
authProperties
);
}
}
}
and in Startup.cs
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.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = Context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddControllers();
//services.AddControllersWithViews();
services.AddDbContext<ApiSiteDbContext>();
services.AddIdentity<AppUser, AppRole>(option =>
{
option.Password.RequireDigit = true;
option.Password.RequiredLength = 6;
option.Password.RequiredUniqueChars = 0;
option.Password.RequireLowercase = true;
option.Password.RequireNonAlphanumeric = true;
option.Password.RequireUppercase = true;
option.SignIn.RequireConfirmedEmail = true;
option.Lockout.MaxFailedAccessAttempts = 5;
option.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
}).AddEntityFrameworkStores<ApiSiteDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.LogoutPath = "/api/Account/Logout";
//options.LoginPath = "/api/Account/Login";
//options.AccessDeniedPath = "/api/Account/accessDenied";
options.SlidingExpiration = true;
});
services.AddMvc(options => options.EnableEndpointRouting = false)
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_3_0);
services.AddCors();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(x => x.WithOrigins("http://localhost:4200").AllowAnyHeader().AllowAnyMethod().AllowCredentials());
app.UseMvc();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
when i make this variable to check the NameIdentifier if return id or not before i use it in a different controller .
Make sure that the roleName variable is not a null or string empty, because adding cookies depends on this condition
Running .NET Core 2.0
Trying to create an 'auto logon' feature into identity 4 server. The 'auto logon' is from a link sent through email, my website owner wants people to be able to click on that link, and automatically get into their account with no password challenge. I do have several security safeguards in place, which are not listed here.
I have everything working, with one exception. I'm not getting the authentication cookie that I'm expecting to see. Here is the relevant code.
var user = await _userManager.FindByNameAsync(userName);
await this._eventService.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName));
await _signInManager.SignInAsync(user, isPersistent: false);
return Redirect(returnUrl);
The authorization cookie never gets set, so the site is not authorized. When I can't seem to figure out what piece I am missing.
My Startup.cs
protected readonly Model_ProgramSettings _programSettings;
protected readonly Model_SecuritySection _securitySection;
public IConfiguration _configuration { get; }
public IHostingEnvironment _environment { get; }
public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
_configuration = configuration;
_environment = environment;
_securitySection = new Model_SecuritySection();
_configuration.GetSection("SecuritySection").Bind(_securitySection);
_programSettings = new Model_ProgramSettings();
_configuration.GetSection("ProgramSettings").Bind(_programSettings);
}
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
#region Configuration
services.Configure<Model_BaseSettings>(_configuration.GetSection("ProgramSettings"));
services.Configure<Model_ProgramSettings>(_configuration.GetSection("ProgramSettings"));
services.Configure<Model_ConnectionStrings>(_configuration.GetSection("ConnectionStrings"));
services.Configure<Model_SecuritySection>(_configuration.GetSection("SecuritySection"));
services.Configure<Model_SmtpSection>(_configuration.GetSection("SmtpSection"));
#endregion
#region Identity
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(_configuration.GetConnectionString("SqlIdentity"))
);
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 4;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.User.RequireUniqueEmail = false;
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 #`~!%^*()+-={}|[]:;<>?s,.'_#&";
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
#endregion
#region Data Services
services.AddScoped<IMarketAreaService, MarketAreaService>();
services.AddScoped<IRealignService, RealignService>();
services.AddScoped<ISubscriberService, SubscriberService>();
services.AddScoped<IToDoService, ToDoService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IMiscDataService, MiscDataService>();
#endregion
services.AddTransient<IEmailService, EmailService>();
services.AddTransient<IErrorService, ErrorService>();
#region Security
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(_programSettings.DataProtectionKeys));
#endregion
#region Swagger
services.AddTransient<IProfileService, CustomProfileService>();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info { Title = $" {this._programSettings.Site} STS", Version = "v1" });
});
#endregion
services.AddMvc()
.AddJsonOptions(options =>
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
);
var policy = new CorsPolicyBuilder();
policy.AllowAnyHeader();
policy.AllowCredentials();
policy.AllowAnyMethod();
policy.WithOrigins(this._securitySection.WithOrigins.ToArray<string>());
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", policy.Build());
});
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
})
.AddInMemoryApiResources(new List<ApiResource>
{
new ApiResource($"API-{this._programSettings.Site}", $"this._programSettings.Site} API")
})
.AddInMemoryClients(new List<Client>
{
new Client
{
ClientId = $"STS-{this._programSettings.Site}",
ClientName = $"{this._programSettings.Site} STS",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
RedirectUris = this._securitySection.RedirectUris,
PostLogoutRedirectUris = this._securitySection.PostLogoutRedirectUris,
AllowedCorsOrigins = new List<string> { this._securitySection.AllowFrom },
AbsoluteRefreshTokenLifetime = 60 * 60 * 24 * 30,
AccessTokenLifetime = 60 * 60,
AuthorizationCodeLifetime = 60 * 5,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
$"API-{this._programSettings.Site}"
},
}
})
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<CustomProfileService>();
builder.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
app.UseMiddleware<ExceptionMiddleware>();
app.UseStaticFiles();
app.UseIdentityServer();
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Frame-Options", $"ALLOW-FROM {this._securitySection.AllowFrom}");
context.Response.Headers.Add("P3P", "CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"");
await next();
});
app.UseCsp(csp =>
{
csp
.AllowFrames
.FromSelf()
.From(this._securitySection.AllowFrom);
});
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", $" {this._programSettings.Site} STS");
});
app.UseCors("CorsPolicy");
app.UseMvcWithDefaultRoute();
app.UseMvc();
}
My controller declaration:
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IIdentityServerInteractionService _interaction;
private readonly IClientStore _clientStore;
private readonly IAuthenticationSchemeProvider _schemeProvider;
private readonly IEventService _eventService;
private readonly IEmailService _emailService;
private readonly IUserService _userService;
private readonly ISubscriberService _subscriberService;
private readonly IMarketAreaService _marketAreaService;
private readonly IDataProtector _dataProtector;
private readonly Model_ProgramSettings _programSettings;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IIdentityServerInteractionService interaction,
IClientStore clientStore,
IAuthenticationSchemeProvider schemeProvider,
IEventService eventService,
IOptions<Model_ProgramSettings> baseSettings,
IUserService userService,
IEmailService emailService,
IDataProtectionProvider dataProtectionProvider,
ISubscriberService subscriberService,
IMarketAreaService marketAreaService)
{
this._userManager = userManager;
this._signInManager = signInManager;
this._interaction = interaction;
this._clientStore = clientStore;
this._schemeProvider = schemeProvider;
this._eventService = eventService;
this._emailService = emailService;
this._userService = userService;
this._subscriberService = subscriberService;
this._programSettings = baseSettings.Value;
this._marketAreaService = marketAreaService;
this._dataProtector = dataProtectionProvider.CreateProtector(_programSettings.Customer);
}
My suggestion is passing the scheme to extension method for SignIn so it knows which handler to use.
var principal = new ClaimsPrincipal(identity);
await this.HttpContext.SignInAsync("idsrv", principal, props);
I am implementing JWT Authorization on Asp.Net but I am having a trouble here. When I am trying to access the Dashboard/Home controller's method I am always recieving '401 Unauthorized' response. According to my research I can say that the request can not go through
[Authorize(Policy = "ApiUser")]
But when comment that statement I recieve an error here
var userId = _caller.Claims.Single(c => c.Type == "id");
Which states that sequence conatains no appropriate elements, and if I will print the
_httpContextAccessor.HttpContext.User.Claims
I basically recieve a '[]'.
Also I want you to notice that the token is
correct
I am testing the application with help of postman
I stacked on this error for two days and really rely on your help. Tell me if some additional code is needed.
Code:
Dashboard Controller:
[Authorize(Policy = "ApiUser")]
[Route("api/[controller]/[action]")]
public class DashboardController : Controller
{
private readonly ClaimsPrincipal _caller;
private readonly BackendContext _appDbContext;
private readonly IHttpContextAccessor _httpContextAccessor;
public DashboardController(UserManager<AppUser> userManager, BackendContext appDbContext, IHttpContextAccessor httpContextAccessor)
{
_caller = httpContextAccessor.HttpContext.User;
_appDbContext = appDbContext;
_httpContextAccessor = httpContextAccessor;
}
// GET api/dashboard/home
[HttpGet]
public async Task<IActionResult> Home()
{
// retrieve the user info
//HttpContext.User
//return new OkObjectResult(_httpContextAccessor.HttpContext.User.Claims);
var userId = _caller.Claims.Single(c => c.Type == "id");
var customer = await _appDbContext.Customers.Include(c => c.Identity).SingleAsync(c => c.Identity.Id == userId.Value);
return new OkObjectResult(new
{
Message = "This is secure API and user data!",
customer.Identity.FirstName,
customer.Identity.LastName,
customer.Identity.PictureUrl,
customer.Identity.FacebookId,
customer.Location,
customer.Locale,
customer.Gender
});
}
}
Startup:
public class Startup
{
private const string SecretKey = "iNivDmHLpUA223sqsfhqGbMRdRj1PVkH"; // todo: get this from somewhere secure
private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<BackendContext>()
.AddDefaultTokenProviders();
services.AddDbContext<BackendContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly("Backend")));
services.AddSingleton<IJwtFactory, JwtFactory>();
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
// Configure JwtIssuerOptions
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(configureOptions =>
{
configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
});
// api user claim policy
services.AddAuthorization(options =>
{
options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
});
// add identity
var builder = services.AddIdentityCore<AppUser>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
});
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<BackendContext>().AddDefaultTokenProviders();
services.AddAutoMapper();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddDbContext<BackendContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly("Backend")));
services.AddTransient<IStoreService, StoreService>();
services.AddMvc();//.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvc();
}
}
Constants (used in startup):
public static class Constants
{
public static class Strings
{
public static class JwtClaimIdentifiers
{
public const string Rol = "rol", Id = "id";
}
public static class JwtClaims
{
public const string ApiAccess = "api_access";
}
}
}
For your issue is caused by the mismatch configuration between token generation and validation for Issuer and Audience.
In Startup.cs, you configure ValidateIssuer = true and ValidateAudience = true, but for your provided token, there is no iss and aud.
Here are two ways for a try:
Disable to validate Issuer and Audience
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidIssuer = "Issuer",
ValidateAudience = false,
ValidAudience = "Audience",
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
Or, provide the Issuer and Audience while generating the token.
public IActionResult Login()
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("iNivDmHLpUA223sqsfhqGbMRdRj1PVkH"));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken(
issuer: "Issuer",
audience: "Audience",
claims: new List<Claim>() { new Claim("rol", "api_access") },
expires: DateTime.Now.AddMinutes(25),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
return Ok(new { Token = tokenString });
}
Here is a working demo CoreJwt.
It has passed about 4 months since this question has been asked, but I hope it will help the future visitors, I had the same problem and it was because I had missed to add the app.UseAuthentication(); to my Configure method:
app.UseAuthentication();
//////////////////////////
app.UseHttpsRedirection();
app.UseStaticFiles();
Also I had missed to add the JwtIssuerOptions to my appsettings.json and appsettings.Development.json:
"JwtIssuerOptions": {
"Issuer": "webApi",
"Audience": "http://localhost:5000/"
},
I'm currently following a tutorial here and so far I have been able to generate JWT tokens successfully. I created an authorized controller (Dashboard), but the response always resulted in 404 unauthorized so I commented it out which resulted in a "Sequence containing no matching element" error being thrown in my Get method; meaning "id" wasn't found but clearly, as seen in the attached image, "id" is present.
Lastly, I tried User.Identity.IsAuthenticated but this always resulted in false. What am I doing wrong?
DashboardController.cs:
namespace JwtTest.API.Controllers
{
// [Authorize(Policy = "ApiUser")]
[Route("api/[controller]/[action]")]
public class DashboardController : Controller
{
private readonly ClaimsPrincipal _caller;
private readonly ApplicationDbContext _appDbContext;
public DashboardController(UserManager<AppUser> userManager, ApplicationDbContext appDbContext, IHttpContextAccessor httpContextAccessor)
{
_caller = httpContextAccessor.HttpContext.User;
_appDbContext = appDbContext;
}
// GET api/dashboard/home
[HttpGet]
public async Task<IActionResult> Home()
{
var userId = _caller.Claims.Single(c => c.Type == "id");
var customer = await _appDbContext.Customers.Include(c => c.Identity).SingleAsync(c => c.Identity.Id == userId.Value);
return new OkObjectResult(new
{
Message = "This is secure API and user data!",
customer.Identity.FirstName,
customer.Identity.LastName,
customer.Identity.PictureUrl,
customer.Identity.FacebookId,
customer.Location,
customer.Locale,
customer.Gender
});
}
}
}
Startup.cs:
namespace JwtTest.API {
public class Startup {
private const string SecretKey = "iNivDmHLpUA223sqsfhqGbMRdRj1PVkH";
private readonly SymmetricSecurityKey _signingKey
= new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
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>
(x => x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
services.AddSingleton<IJwtFactory, JwtFactory>();
services.TryAddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddMvc();
// services.AddIdentity<AppUser, IdentityRole>()
// .AddEntityFrameworkStores<ApplicationDbContext>()
// .AddDefaultTokenProviders();
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
services.Configure<JwtIssuerOptions>(options => {
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
var tokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(configureOptions => {
configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
});
var builder = services.AddIdentityCore<AppUser>(o => {
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
});
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
// api user claim policy
services.AddAuthorization(options => {
options.AddPolicy("ApiUser", policy =>
policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
});
services.AddAutoMapper();
services.AddMvc().AddFluentValidation(fv =>fv.RegisterValidatorsFromAssemblyContaining<Startup>());
}
// 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.UseExceptionHandler(
builder =>
{
builder.Run(
async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
context.Response.AddApplicationError(error.Error.Message);
await context.Response.WriteAsync(error.Error.Message).ConfigureAwait(false);
}
});
});
app.UseAuthentication();
app.UseMvc();
}
}
}
AuthController:
namespace JwtTest.API.Controllers {
[Route("api/[controller]")]
public class AuthController : Controller {
private readonly UserManager<AppUser> _userManager;
private readonly IJwtFactory _jwtFactory;
private readonly JwtIssuerOptions _jwtOptions;
public AuthController(UserManager<AppUser> userManager, IJwtFactory jwtFactory, IOptions<JwtIssuerOptions> jwtOptions) {
_jwtFactory = jwtFactory;
_jwtOptions = jwtOptions.Value;
_userManager = userManager;
}
[HttpPost("login")]
public async Task<IActionResult> Post([FromBody]CredentialsViewModel credentials)
{
if(!ModelState.IsValid) return BadRequest(ModelState);
var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password);
if(identity == null)
return BadRequest(Errors.AddErrorToModelState("login_failure", "Invalid username or password", ModelState));
var jwt = await Tokens.GenerateJwt(identity, _jwtFactory, credentials.UserName, _jwtOptions, new JsonSerializerSettings {
Formatting = Formatting.Indented
});
return new OkObjectResult(jwt);
}
private async Task<ClaimsIdentity> GetClaimsIdentity(string userName, string password)
{
if(string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
return await Task.FromResult<ClaimsIdentity>(null);
var userToVerify = await _userManager.FindByNameAsync(userName);
if(userToVerify == null) return await Task.FromResult<ClaimsIdentity>(null);
if(await _userManager.CheckPasswordAsync(userToVerify, password))
return await Task.FromResult(_jwtFactory.GenerateClaimsIdentity(userName, userToVerify.Id));
return await Task.FromResult<ClaimsIdentity>(null);
}
}
}
AccountsController:
namespace JwtTest.API.Controllers {
[Route("api/[controller]")]
public class AccountsController : Controller {
private readonly UserManager<AppUser> _userManager;
private readonly IMapper _mapper;
private readonly ApplicationDbContext _appDbContext;
public AccountsController(UserManager<AppUser> userManager, IMapper mapper, ApplicationDbContext appDbContext) {
_appDbContext = appDbContext;
_mapper = mapper;
_userManager = userManager;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] RegistrationViewModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var userIdentity = _mapper.Map<AppUser>(model);
var result = await _userManager.CreateAsync(userIdentity, model.Password);
if(!result.Succeeded) return new BadRequestObjectResult(Errors.AddErrorsToModelState(result, ModelState));
await _appDbContext.Customers.AddAsync(new Customer {
IdentityId = userIdentity.Id,
Location = model.Location
});
await _appDbContext.SaveChangesAsync();
return new OkObjectResult("Account created");
}
}
}
I had the same issue with this code.
Make sure that appsettings.json is on the main folder of the project. (Same as startup.cs).
Since there are setting of audience and issuer that are not being imported. That results to a fail in authentication.
That should fix your issue.