Reputation: 1193
I want to secure my API endpoint, so that It can only be access over authentication, I end up getting this error. I use the register method to register a user and get a token. I then use that long token in the request header to access a protected area. But I keep getting authorize 401 error. What exactly is happening wrong!
http Get http://localhost:5000/Account/Protected 'authorization:Bearer eyJhb....fx0IM'
HTTP/1.1 401 Unauthorized
Content-Length: 0
Date: Fri, 27 Jul 2018 12:36:46 GMT
Server: Kestrel
WWW-Authenticate: Bearer error="invalid_token", error_description="The token as no expiration"
I have this controller configuration for the for the Account Controller
. The Register method works well and register the person, now if I want to add a test the api with the Protected Controller. I get the 401 error.
namespace Lemon.Auth.Controllers
{
[Route("[controller]/[action]")]
public class AccountController : ControllerBase
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly IConfiguration _configuration;
public AccountController(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
IConfiguration configuration
)
{
_userManager = userManager;
_signInManager = signInManager;
_configuration = configuration;
}
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)
[HttpGet]
public async Task<object> Protected()
{
return "Protected area";
}
// Handlers
[HttpPost]
public async Task<object> Login([FromBody] LoginDto model)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, false);
if(result.Succeeded)
{
var appUser = _userManager.Users.SingleOrDefault(r => r.Email == model.Email);
return await GenerateJwtToken(model.Email, appUser);
}
throw new ApplicationException("Invalid Login Attempt");
}
// Handler :Register:
public async Task<object> Register([FromBody] RegisterDto model)
{
var user = new IdentityUser
{
UserName = model.Email,
Email = model.Email
};
// debuggi
try
{
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, false);
return await GenerateJwtToken(model.Email, user);
}
}
catch (System.Exception ex)
{
Console.WriteLine(ex.ToString());
}
throw new ApplicationException("Unknown Error");
}
private async Task<object> GenerateJwtToken(string email, IdentityUser user)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, user.Id)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
// var expires = DateTime.Now.AddDays(Convert.ToDouble(_configuration["JwtExpiresDays"]));
Console.WriteLine("hello");
var token = new JwtSecurityToken(
_configuration["JwtIssuer"],
_configuration["JwtIssuer"],
claims,
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
}
This is my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Db and context
services.AddEntityFrameworkNpgsql().AddDbContext<ApplicationDbContext>(options =>
{
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"));
}
);
// add Identity
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// add jwt
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // clear default behaviour
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
});
// add mvc
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext dbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
// app.UseHttpsRedirection();
app.UseMvc();
app.UseAuthentication();
// ensure tables are created
dbContext.Database.EnsureCreated();
}
All I want to achieve is to protect the API.. I consulted this tutorial https://medium.com/@ozgurgul/asp-net-core-2-0-webapi-jwt-authentication-with-identity-mysql-3698eeba6ff8
Upvotes: 1
Views: 4945
Reputation: 21033
You should be using Authentication before you are using MVC:
app.UseAuthentication();
app.UseMvc();
The HTTP Pipeline Executes in Order, here is a link to the docs for more info
Upvotes: 1
Reputation: 9845
Edit-2: I just saw the tutorial that you are using. It already does the same. Can you try to add an expiration date to the token? The error message says token has no expiration.
private async Task<object> GenerateJwtToken(string email, IdentityUser user)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, user.Id)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
// var expires = DateTime.Now.AddDays(Convert.ToDouble(_configuration["JwtExpiresDays"]));
Console.WriteLine("hello");
var token = new JwtSecurityToken(
_configuration["JwtIssuer"],
_configuration["JwtIssuer"],
claims,
expires: DatimeTime.UtcNow.AddHours(1), // or smth else
signingCredentials: creds
);
Edit: My first answer was also partially a cause of the issue but not yet. The actual issues is that services.AddIdentity<,>
adds cookie authentication as can been seen here. If you insist on using asp.net-identity you will have to make some changes. An example can be found here.
Old: Your authentication doesn't work because you add the authentication after mvc. Just flip it
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext dbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
// app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
// ensure tables are created
dbContext.Database.EnsureCreated();
}
Upvotes: 4