JWT - Always unauthorized - asp.net-core

I have a very simple code for the user to login as following:
[HttpPost]
[Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel model)
{
var user = await userManager.FindByEmailAsync(model.Email);
if (user != null && await userManager.CheckPasswordAsync(user, model.Password))
{
var userRoles = await userManager.GetRolesAsync(user);
var authClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
foreach (var userRole in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
}
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));
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)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
This sends me the token as expected
When I use the Token to send a request to a simple controller I get 401-Unauthorized
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
[Route("getme")]
public async Task<IActionResult> Get()
{
return Ok("Good");
}
}
This is my Startup.cs
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ConnStr")));
// For Identity
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Adding Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
// Adding Jwt Bearer
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
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"]))
};
});
and the following is my appsettings
I can verify the token at https://jwt.io/
For some reason when I send a request to TestController it always sends back an Unauthorized response. Any idea what am I doing wrong?

If this helps anyone, The issue was in my Startup file under Configure section I had UseAuthentication after UseAuthorization which cause the issue. The correct order as following fixed the issue
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseHttpsRedirection();

Related

.NET Authentication user via cookie and not via header Bearer

I am looking for a way to authenticate the user via a JWT cookie and not through the authorization header.
I have been looking around but couldn't find an answer I managed to work with.
Currently I am authenticating like this:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/chat")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
What is the equivalent to authenticating the same way but with a cookie called JWT for instance?
Here is a whole working demo you could follow:
Generate token:
[Route("api/[Controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private IConfiguration _config;
public ValuesController(IConfiguration config)
{
_config = config;
}
[Route("GenerateToken")]
public async Task<IActionResult> GenerateToken()
{
//add claims by yourself...
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "Admin")
};
var token = new JwtSecurityToken(_config["Jwt:JwtIssuer"],
_config["Jwt:JwtIssuer"],
claims: claims,
expires: DateTime.Now.AddDays(5),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:JwtKey"])),
SecurityAlgorithms.HmacSha256));
var data = new JwtSecurityTokenHandler().WriteToken(token);
HttpContext.Response.Cookies.Append("access_token", data, new CookieOptions { HttpOnly = true });
return Ok(new { data });
}
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var tokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Jwt:JwtIssuer"],
ValidAudience = Configuration["Jwt:JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:JwtKey"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
};
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = tokenValidationParameters;
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var token = context.HttpContext.Request.Cookies["access_token"];
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
return Task.CompletedTask;
}
return Task.CompletedTask;
}
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseRouting();
app.UseAuthentication(); //the middleware order must like here
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
appsettings.json:
{
"jwt": {
"JwtKey": "JWT_KEYsomethingyouwantwhichissecurewillworkk",
"JwtIssuer": "https://xxxxxx.com",
"JwtExpireDays": 15
}
}
After you hit the generate token method, you could set the cookie for the token. Then you can access the method which is declared by authorize attribute:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[Authorize] //be sure add this...
[HttpGet]
public string Get()
{
return "Got in";
}
}
Result:

Using JWT Token in Client API

I have an API in .NET5 uses JWTBearer to secure the API. Now I wanted to configure my client app to use the token generated from the api/gettoken. It works fine in swagger, but I don't know how to configure my MVC and API(consuming API) to use this token. Can somebody help by providing the configureservices and configure methods in the startup
To Glenn,
I have 3 projects JWT.IDP, JWT.API, JWT.MVC. JWT.IDP issues the token, my intention is to use that token in JWT.API and call the JWT.API function from JWT.MVC. The IDP is working perfectly, I can generate the token and my JWT.MVC Login controller is able to receive it. The last function in the below code (GetWeatherData) is coded according to the idea you have given. If I don't pass the token, I used to get 401 error, now I get 500 Internal Server Error
namespace JWT.MVC.Controllers
{
public class LoginController : Controller
{
public IActionResult DoLogin()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DoLogin([Bind("EmailOrName,Password")] LoginRequestModel loginRequestModel)
{
var apiName = $"https://localhost:44318/api/User/login";
HttpClient httpClient = new HttpClient();
HttpResponseMessage response = await httpClient.PostAsJsonAsync(apiName, loginRequestModel);
var jasonString = await response.Content.ReadAsStreamAsync();
var data = await JsonSerializer.DeserializeAsync<IEnumerable<AccessibleDb>>
(jasonString, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
foreach (var item in data)
{
item.UserName = loginRequestModel.EmailOrName;
}
return View("SelectDatabase" , data);
}
public async Task<IActionResult> PostLogin(string db, string user)
{
TokenRequestModel tokenRequestModel = new TokenRequestModel() { Database = db, UserName = user };
var apiName = $"https://localhost:44318/api/User/tokenonly";
HttpClient httpClient = new HttpClient();
HttpResponseMessage response = await httpClient.PostAsJsonAsync(apiName, tokenRequestModel);
var jasonString = await response.Content.ReadAsStreamAsync();
var data = await JsonSerializer.DeserializeAsync<AuthenticationModel>
(jasonString, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
var stream = data.Token;
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(stream);
var tokenS = jsonToken as JwtSecurityToken;
var selectedDb = tokenS.Claims.First(claim => claim.Type == "Database").Value;
ViewBag.SelectedDb = selectedDb;
return View(data);
}
public async Task<IActionResult> GetWeatherData(string token)
{
var apiName = $"https://localhost:44338/weatherforecast";
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await httpClient.GetAsync(apiName);
if (!response.IsSuccessStatusCode)
{
ViewBag.Error = response.StatusCode;
return View("Weatherdata");
}
var jasonString = await response.Content.ReadAsStreamAsync();
var data = await JsonSerializer.DeserializeAsync<WeatherForecast>
(jasonString, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
return View("Weatherdata" , data);
}
}
}
Startup class for JWT.MVC is as below
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Audience = "SecureApiUser";
options.Authority = "https://localhost:44318";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}
Startup class for JWT.API is as below
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//Copy from IS4
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Audience = "SecureApiUser";
options.Authority = "https://localhost:44318";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
//End
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JWT.API", Version = "v1" });
});
}
Startup class for JWT.IDP is as below
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//Configuration from AppSettings
services.Configure<JwtSettings>(Configuration.GetSection("JWT"));
//User Manager Service
services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<IdentityDbContext>();
services.AddScoped<IUserService, UserService>();
//Adding DB Context with MSSQL
services.AddDbContext<IdentityDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("IdentityDbConnectionString"),
b => b.MigrationsAssembly(typeof(IdentityDbContext).Assembly.FullName)));
//Adding Athentication - JWT
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.RequireHttpsMetadata = false;
o.SaveToken = false;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(Convert.ToInt32(Configuration["JWT:DurationInMinutes"])),
ValidIssuer = Configuration["JWT:Issuer"],
ValidAudience = Configuration["JWT:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Key"]))
};
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JWT.IDP", Version = "v1" });
});
}
And the JWT Setting is as below
"JWT": {
"key": "C1CF4B7DC4C4175B6618DE4F55CA4",
"Issuer": "http://localhost:44318",
"Audience": "SecureApiUser",
"DurationInMinutes": 60
},
The short answer is
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", [token])
Keep "Bearer" as it is, it's just a constant. Replace [token], with the base 64 encoded token value returned to us from the OAuth protocol you are using.

Cannot access [Authorize] controllers with cookie authentication

I'm using .NET Core 3.1 and Cookie authentication to login.
I saw the cookie generated as the picture below, but I still cannot access [Authorize] controllers.
This is my code in Startup.cs
ConfigureServices(IServiceCollection services)
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Lax;
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.Name = "SRLoginCookie";
options.Cookie.HttpOnly = true;
options.LoginPath = new PathString("/users/login");
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.SlidingExpiration = false;
});
services.PostConfigure<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme,
options =>
{
options.LoginPath = "/users/login";
});
Configure(IApplicationBuilder app)
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
Login code
[HttpPost]
[Route("login")]
[AllowAnonymous]
public async Task<ActionResult> Login([FromForm]LoginRequest model)
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Email, model.Email));
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
principal);
//if (HttpContext.User.Identity.IsAuthenticated)
return RedirectToAction("Index", "Home");
}
The cookie was generated, but can't access home/index as you see. What is my problem? Many thanks for help!
I found the solution after posting this question for a while.
Just need [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] instead of [Authorize], everthing solved.
Try this:
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
// get token from cookie
if (context.Request.Cookies.ContainsKey("SRLoginCookie"))
{
context.Token = context.Request.Cookies["SRLoginCookie"];
}
return Task.CompletedTask;
}
};
});

Authorize Attribute not working with JWT Access Token in ASP.Net Core

Trying to setup JWT with Ast.Net Core app and somehow when I use the [Authorize] attribute on the method it shows Bearer error="invalid_token"
Not sure what I am missing here.
AppSettings:
"Jwt": {
"Key": "ThisisaKeyforAPIAccess",
"Issuer": "TestSite.com"
}
Method to generate Access Token:
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Issuer"],
expires: DateTime.Now.AddMinutes(10),
signingCredentials: credentials);
return new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
};
Auth.cs (To Check Token)
public static IServiceCollection AddAuthentication(this IServiceCollection services, IConfiguration configuration)
{
var issuerID = configuration.GetSection("Jwt").GetValue<string>("Issuer");
services.AddAuthentication(
option => {
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}
).
AddJwtBearer(options => {
options.SaveToken = true;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = issuerID,
ValidIssuer = issuerID,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]))
};
});
return services;
}
and finally in Startup.cs
services.AddAuthentication(_configuration);
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build();
});
and in Configure method for Startup class
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseAuthentication();
app.UseMvc();
}
If I use the [Authorize] attribute I am getting invalid token on the method. Not sure what I am missing here.

How do I Configure JWT Tokens in ASP.NET CORE Identity?

New to this. Spent a lot of hours on it. I am trying to add a JWT issuer to an existing site so i can dev a mobile app. I have added this to Startup:
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
.RequireAuthenticatedUser().Build());
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
options.TokenValidationParameters =
new TokenValidationParameters
{
ClockSkew = TimeSpan.FromMinutes(5),
ValidateIssuer = false,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
RequireExpirationTime = true,
ValidIssuer = "https://URL",
ValidAudience = "https://URL",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("fbsghfdgjdgfjdbjgbjdbgjdbgjbdfjgbdfgjdbgjbfjdgbjdfgb"))
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Console.WriteLine("OnAuthenticationFailed: " + context.Exception.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Console.WriteLine("OnTokenValidated: " + context.SecurityToken);
return Task.CompletedTask;
}
};
});
I have this in the account controller to dish a token out. I am a bit confused on how it should work, should I use the same login logic the login action uses, via usermanager? Or just check username and password and send a token back?
[HttpPost]
[Route("/token3")]
[AllowAnonymous]
public async Task<IActionResult> Token3(TokenRequest model)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByEmailAsync(model.username);
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync
(user, model.password, lockoutOnFailure: false);
if (!result.Succeeded)
{
return Unauthorized();
}
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, model.username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("fbsghfdgjdgfjdbjgbjdbgjdbgjbdfjgbdfgjdbgjbfjdgbjdfgb"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "https:/URL",
audience: "https://URL",
claims: claims,
expires: DateTime.Now.AddDays(7),
signingCredentials: creds);
return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
//return new JwtSecurityTokenHandler().WriteToken(token);
}
}
return BadRequest("Could not verify username and password");
}
Now this returns me a token if I call it with username/password in headers. But if i try to use the token I get errors and redirected to login. Main info from remote debugger gives me this:
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Failed to validate the token <TOKEN>
IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed for user: (null).
My test action:
[HttpGet]
[Route("/test")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Test()
{
return Ok("ok");
}