c# Web API 500 error from method after adding authentication - c#

I have been trying to fix this all day. I am making a test API to practice my development. I tried adding a bearer authentication error and now none of the methods work.
namespace WebApiTest
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
});
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header. \r\n\r\n Enter the token in the text input below."
});
c.OperationFilter<AddAuthorizationHeaderParameterOperationFilter>();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
try
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Web API Test");
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while configuring the application.");
throw;
}
}
}
public class AddAuthorizationHeaderParameterOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var authAttributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<AuthorizeAttribute>();
if (authAttributes.Any())
{
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" },
},
new string[] {}
}
}
};
}
}
}
}
namespace AzureWebApiTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MainController : ControllerBase
{
private readonly LoginRequest loginInformation = new LoginRequest("username", "password");
[HttpPost("GetToken")]
public IActionResult GetToken([FromBody] LoginRequest loginRequest)
{
if (loginRequest == null)
{
return BadRequest("Bad Login Request");
}
if (loginRequest.Equals(loginInformation))
{
var token = GenerateBearerToken(loginRequest);
return Ok(new { token });
}
else
{
return Unauthorized("Incorrect Login Information");
}
}
[HttpGet("GetHello")]
[Authorize(AuthenticationSchemes = "Bearer")]
public IActionResult GetHello([FromQuery] string name)
{
try
{
var token = Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
var tokenHandler = new JwtSecurityTokenHandler();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("secretKey"))
};
var claimsPrincipal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
return Ok("Hello " + name);
}
catch (SecurityTokenExpiredException)
{
return Unauthorized("Token has expired.");
}
catch (SecurityTokenInvalidSignatureException)
{
return Unauthorized("Invalid token signature.");
}
catch (Exception)
{
return Unauthorized("Invalid token.");
}
}
private string GenerateBearerToken(LoginRequest loginRequest)
{
if (ValidateCredentials(loginRequest))
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("secretKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
return null;
}
private bool ValidateCredentials(LoginRequest loginRequest)
{
if(loginRequest.Equals(loginInformation))
{
return true;
}
return false;
}
}
}
When I try the method in swagger, I am getting the response:
I've tried getting ChatGPT to fix it but I'm getting nowhere and it's going in circles. Anyone have any ideas?
Edit:
The Validate function returns false at the end, I changed it for testing purposes. Edited back.

You are trying to get authorization to work, but you lack a few things.
Authentication
services.AddAuthentication(..options..).AddJwtBearer(..options..)
You need to add authentication to the pipeline, by adding UseAuthentication() before UseAuthorization(), like:
app.UseAuthentication();
app.UseAuthorization();
You need to add / register the authorization service using
services.AddAuthorization(..options..);

Related

How to properly setup JwtBearerOptions

I setup Identity Server 4 to issue JWT tokens to authenticate users. In Identity Server 4 I have setup the following:
public class Resources {
public static IEnumerable<ApiResource> GetResources() {
return new[] {
new ApiResource {
Name = "Test.API",
DisplayName = "Test API",
Description = "Allow the user access to the test API",
Scopes = new List<string> { "Core API" },
UserClaims = new List<string> { "General", "Admin" }
}
};
}
public static IEnumerable<ApiScope> GetScopes() {
return new[] {
new ApiScope("Core.API", "Allow access to the test API")
};
}
public static IEnumerable<Client> GetClients() {
return new List<Client>() {
new Client {
ClientName = "Test Client",
ClientId = "b778a2ad-090d-4525-8954-6411de2cd339",
ClientSecrets = new List<Secret> { new Secret("random_text".Sha512()) },
AllowedScopes = new List<string> { "Core.API" },
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
},
new Client {
ClientName = "Test Web App",
ClientId = "abb9c89c-a018-4b0f-9a0f-4e701c637665",
ClientSecrets = new List<Secret> { new Secret("other_random_text".Sha512()) },
AllowedGrantTypes = GrantTypes.Hybrid,
RequirePkce = false,
AllowRememberConsent = false,
AllowedScopes = new List<string>
{
StandardScopes.OpenId,
StandardScopes.Profile,
StandardScopes.Address,
StandardScopes.Email,
"Core.API",
"roles"
}
}
};
}
public static IEnumerable<IdentityResource> GetIdentities() {
return new[] {
new IdentityResources.Email(),
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource {
Name = "User Role",
UserClaims = new List<string> { "Admin", "General" }
}
};
}
}
public class Startup {
public Startup(IConfiguration configuration, IWebHostEnvironment appEnv) {
Configuration = configuration;
CurrentEnvironment = appEnv;
}
public IConfiguration Configuration { get; }
private IWebHostEnvironment CurrentEnvironment { get; set; }
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<IUserRequester, UserRequester>(_ =>
new UserRequester(Configuration.GetSection("AzureTableStore.UserLogin").Get<TableStoreConfiguration>()));
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver());
IIdentityServerBuilder builder = services.AddIdentityServer();
if (CurrentEnvironment.IsDevelopment()) {
builder.AddDeveloperSigningCredential();
} else {
X509Certificate2 certData = DownloadCertificate(Configuration.GetSection("APICertificate").Get<Secret>());
builder.AddSigningCredential(certData);
}
builder.AddInMemoryClients(Resources.GetClients());
builder.AddInMemoryIdentityResources(Resources.GetIdentities());
builder.AddInMemoryApiResources(Resources.GetResources());
builder.AddInMemoryApiScopes(Resources.GetScopes());
builder.Services.Configure<TableStoreConfiguration>(Configuration.GetSection("AzureTableStore.UserLogin"));
builder.Services.Configure<RedisConfiguration>(Configuration.GetSection("RedisCache"));
builder.Services.AddTransient<IRedisConnection, RedisConnection>();
builder.Services.AddTransient<IUserRequester, UserRequester>();
builder.Services.AddTransient<IProfileService, ProfileService>();
builder.Services.AddTransient<IResourceOwnerPasswordValidator, PasswordValidator>();
builder.Services.AddTransient<IAuthorizationCodeStore, AuthorizationCodeStore>();
builder.Services.AddTransient<IReferenceTokenStore, ReferenceTokenStore>();
builder.Services.AddTransient<IRefreshTokenStore, RefreshTokenStore>();
builder.Services.AddTransient<IUserConsentStore, UserConsentStore>();
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo {
Version = "v1",
Title = "Authentication",
Description = "API allowing for user requests to be authenticated against their credentials",
Contact = new OpenApiContact {
Name = "Me",
Email = "me#fake.com"
}
});
string xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
string xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
services.AddCors(options => options.AddDefaultPolicy(
builder => builder.AllowAnyOrigin().
SetIsOriginAllowedToAllowWildcardSubdomains().
AllowAnyMethod().
AllowAnyHeader().
WithHeaders("X-TEST", "true")));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Authentication API v1"));
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}"));
}
private static X509Certificate2 DownloadCertificate(Secret secret) {
KeyVaultSecret secretValue = new Provider(secret.KeyVaultName).GetSecretAsync(secret.SecretName).Result;
var store = new Pkcs12Store();
using (Stream stream = secret.KeyVaultName.Equals("local")
? new FileStream(Environment.GetEnvironmentVariable(secret.SecretName), FileMode.Open)
: new MemoryStream(Convert.FromBase64String(secretValue.Value))) {
store.Load(stream, Array.Empty<char>());
}
string keyAlias = store.Aliases.Cast<string>().SingleOrDefault(a => store.IsKeyEntry(a));
var key = (RsaPrivateCrtKeyParameters)store.GetKey(keyAlias).Key;
var certificate = new X509Certificate2(
DotNetUtilities.ToX509Certificate(store.GetCertificate(keyAlias).Certificate));
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(DotNetUtilities.ToRSAParameters(key));
return RSACertificateExtensions.CopyWithPrivateKey(certificate, rsa);
}
}
and in the Startup.cs of all of my services I have the following:
services.AddAuthentication(configuration.SchemeType).
AddJwtBearer("Bearer", options => {
options.Authority = "https://mytest.com/auth"; // Endpoint of the authentication service
options.TokenValidationParameters = new TokenValidationParameters {
ValidateAudience = false
};
});
// Ensure that the claim type is verified as well
services.AddAuthorization(options => options.AddPolicy("ClientIdPolicy", policy =>
policy.RequireClaim("client_id", "b778a2ad-090d-4525-8954-6411de2cd339", "abb9c89c-a018-4b0f-9a0f-4e701c637665")));
The problem I'm having is that this consistently fails. After trying to debug the issue, I've come to the realization that I don't really understand the purpose of this. Is it validating the fields on the JWT to ensure they're valid? If so, what value should I provide for Authority? Are there any other fields I need to set?
Update:
Upon further investigation, I see that requests return with a WWW-Authenticate response header that contains Bearer error="invalid_token", error_description="The signature key was not found". It appears that I've misconfigured either my Authentication service or my downstream services but I'm not sure which.
Thanks to the article provided by #MichalTrojanowski as well as this post, I was able to determine that there were two problems with how I was authenticating JWTs:
I had my authority set to the wrong value. Or rather, my value for Authority matched the actual endpoint for authorizing tokens but that value did not match what was printed in /.well-known/openid-configuration. Therefore, the authentication failed.
My issuer did not match the iss value in the JWT.
After fixing these two problems, my services have been able to authenticate properly.

My Web API keeps returning 401 Unauthorized despite the inclusion of jwt in the request

I have a web API that allows registration, login and retrieval of an entity, students data in this case. I added Jwt token based authentication that issues a Jwt on successful login. The problem is I keep getting 401 Unauthorized either or not I included the token in my request to the server. I saw a similar issue here but that doesn't work for me.
The Controller class
[ApiController]
[Route("api/student")]
public class StudentController:ControllerBase
{
IStudentRepository _student;
IMapper _mapper;
IConvertFileToByteArray _fileConverter;
IAuthenticate _authenticate;
public StudentController(IMapper mapper, IStudentRepository context,IConvertFileToByteArray converter,IAuthenticate authenticate)
{
_student = context;
_mapper = mapper;
_fileConverter = converter;
_authenticate = authenticate;
}
[HttpGet("login")]
[PasswordAttribute]
[AllowAnonymous]
public async Task<IActionResult> Login([FromQuery] LoginCredential credential)
{
Student student;
try
{
student = await _student.GetStudentAsync(credential.Username);
if (!_authenticate.AuthenticateUser(student.Password, credential.Password))
{
return Unauthorized("Incorrect Username or Password");
}
var token=_authenticate.GenerateToken(credential.Username);
return Ok(new { Token = token });
}
catch (Exception e)
{
return Unauthorized(e.Message);
}
}
[HttpGet("usrname",Name ="GetStudentUsingUsername")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<ActionResult<ReturnStudent>> Get([FromQuery]string Username)
{
Console.WriteLine("GetReq");
Student student;
IFormFile passport=null;
try
{
student = await _student.GetStudentAsync(Username);
Console.WriteLine(student.FirstName);
if (student.ByteArrayofPassport.Length>1000)
{
passport = new FormFile(new MemoryStream(student.ByteArrayofPassport), 0, student.ByteArrayofPassport.Length, "ByteArrayOfPassport", "passport");
}
ReturnStudent returnStudent = _mapper.Map<ReturnStudent>(student);
returnStudent.Passport = passport;
return Ok(returnStudent);
}
catch (Exception e)
{
return Ok(e.Message);
}
}
Token is generated through the method
private readonly byte[] secret = Encoding.ASCII.GetBytes("REDACTED");
public string GenerateToken(string username)
{
var tokenHandler=new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, username) }),
Expires =DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(secret), SecurityAlgorithms.HmacSha256)
};
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return token;
}
Service Configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
/*services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "StudentManagementSystem2", Version = "v1" });
});*/
services.AddDbContext<StudentContext>();
services.AddScoped<IStudentRepository,StudentRepository>();
services.AddScoped<IConvertFileToByteArray, ConvertFileToByteArray>();
services.AddScoped<IAuthenticate, Authenticate>();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("REDACTED"))
};
});
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseSwagger();
///app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "StudentManagementSystem2 v1"));
}
//app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

Return json from .net core api when using NotFound()

I'm trying to make my web api core return application/json, but it strangely always returns this html page breaking the error convention established by the team.
Here's the code i'm trying to execute but with no success at all so far:
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeFormat = "dd/MM/yyyy" });
});
services.AddMvcCore().AddRazorViewEngine().AddRazorRuntimeCompilation().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = actionContext =>
{
var errorList = (from item in actionContext.ModelState
where item.Value.Errors.Any()
select item.Value.Errors[0].ErrorMessage).ToList();
return new BadRequestObjectResult(new
{
ErrorType = "bad_request",
HasError = true,
StatusCode = (int)HttpStatusCode.BadRequest,
Message = "Formato do request inválido",
Result = new
{
errors = errorList
}
});
};
});
}
// 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.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseCors(
options => options.AllowAnyOrigin().SetIsOriginAllowed(x => _ = true).AllowAnyMethod().AllowAnyHeader()
);
app.UseHttpsRedirection();
app.UseRouting();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
AuthController.cs
[HttpPost("refreshtoken")]
public IActionResult PostRefreshToken(Guid token)
{
if (!_authTokenService.IsValid(token))
{
return NotFound(new JsonResponse
{
HasError = true,
ErrorType = "not_found",
StatusCode = (int)HttpStatusCode.NotFound,
Title = "Token não encontrado",
Message = "refresh is not valid because it was not found or does not comply",
});
}
var savedToken = _authTokenService.Get(token);
...
return Ok(new JsonResponse
{
StatusCode = (int)HttpStatusCode.OK,
Title = "Token atualizado",
Message = "jwt access token refreshed with success, please update your keys for subsequent requests",
Result = new
{
Expiration = accessToken.Expiration.ToString("dd/MM/yyyy HH:mm:ss"),
AccessToken = accessToken.Token,
RefreshToken = refreshToken.Token,
}
});
}
when this code is executed i was expecting a json result when NotFound() block is reached, but instead it returns this text/html page
ErrorHandlingMiddleware.cs
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError;
var result = JsonConvert.SerializeObject(new
{
HasError = true,
StatusCode = (int)code,
Message = ex.Message
}, new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
}
});
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
In my case it ended up being the visual studio extension Conveyor by Keyoti being the culprit of the errors aforementioned.
When i disabled the extension, the code was revealed to be ok and returning the right code, a json object body sent by the server.

Custom middleware with JWT authorization - IsAuthenticated=False

I wrote a small middleware code (asp.net core v2.2 + c#) that run AFTER a call to server is executed and run some logic if the user is authenticated. Since it's WebAPI - the authentication is done by using Bearer token.
This how the middleware looks like:
public class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
await _next(httpContext).ConfigureAwait(false); // calling next middleware
if (httpContext.User.Identity.IsAuthenticated) // <==================== Allways false
{
// Do my logics
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
The problem is that the expression httpContext.User.Identity.IsAuthenticated allways return false, even if the request successfully authenticated with the service.
My Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
app.UseAuthentication();
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
// Formatting numbers, dates, etc.
SupportedCultures = new[] { new CultureInfo("en-US") },
// UI strings that we have localized.
SupportedUICultures = supportedCultures,
});
app.UseMvc();
app.UseMyMiddleware(ConfigurationManager.ApplicationName);
}
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication().AddJwtBearer(options =>
{
// ...
});
}
I also checked that the httpContext.Request object contain the Authorization header and it does.
Why the httpContext.User object seems like the request is unauthorized?
Here is a simple demo like below:
1.Generate the token:
[Route("api/[controller]")]
[ApiController]
public class LoginController : Controller
{
private IConfiguration _config;
public LoginController(IConfiguration config)
{
_config = config;
}
[AllowAnonymous]
[HttpPost]
public IActionResult Login([FromBody]UserModel login)
{
IActionResult response = Unauthorized();
var user = AuthenticateUser(login);
if (user != null)
{
var tokenString = GenerateJSONWebToken(user);
response = Ok(new { token = tokenString });
}
return response;
}
private string GenerateJSONWebToken(UserModel userInfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim>{
new Claim(JwtRegisteredClaimNames.Sub, userInfo.Username),
new Claim(JwtRegisteredClaimNames.Email, userInfo.EmailAddress),
new Claim("DateOfJoing", userInfo.DateOfJoing.ToString("yyyy-MM-dd")),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private UserModel AuthenticateUser(UserModel login)
{
UserModel user = null;
//Validate the User Credentials
//Demo Purpose, I have Passed HardCoded User Information
if (login.Username == "Jignesh")
{
user = new UserModel { Username = "Jignesh Trivedi", EmailAddress = "test.btest#gmail.com" };
}
return user;
}
}
2.Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...
app.UseMyMiddleware();
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseMvc();
}
3.custom MyMiddleware(the same as yours)
4.Authorize api:
[HttpGet]
[Authorize]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "High Time1", "High Time2", "High Time3", "High Time4", "High Time5" };
}
5.Result:

IdentityServer4 Resource owner password and Win auth: unauthorized

in the last few days I've been reading IdentityServer4 docs and putting together my sample server + sample client using Resource owner password. Now I'd like to add Windows authentication (will be done via Active Directory) in parallel, so the client app (not a web app but a desktop app) could either prompt the user for credentials or login using Windows authentication via Active Directory.
The documentation about Windows Authentication explains how to configure IIS or HTTP.Sys, but what I want is to:
user opens the app
the app use single sign on to post a request to the web api to request token and refresh token
the web api uses windows authentication to validate the identity of the user and returns the token
I've tried to follow this answer, but it doesn't work (It returns unauthorized).
web api: Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
// name of the API resource
options.Audience = "api/user";
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddIdentityServer(options => { options.PublicOrigin = "http://localhost:5000"; options.MutualTls.Enabled = false; })
.AddExtensionGrantValidator<WinAuthGrantValidator>()
.AddDeveloperSigningCredential()
.AddTestUsers(Config.GetUsers())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddInMemoryIdentityResources(Config.GetIdentityResources());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILogger<Startup> logger, IServer server)
{
app.Use(async (context, next) =>
{
context.Features.Get<IHttpMaxRequestBodySizeFeature>()
.MaxRequestBodySize = 10 * 1024;
var serverAddressesFeature =
app.ServerFeatures.Get<IServerAddressesFeature>();
var addresses = string.Join(", ", serverAddressesFeature?.Addresses);
logger.LogInformation($"Addresses: {addresses}");
await next.Invoke();
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// Enable HTTPS Redirection Middleware when hosting the app securely.
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
app.UseIdentityServer();
app.UseAuthentication();
}
}
internal static class Config
{
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "password"
},
new TestUser
{
SubjectId = "2",
Username = "bob",
Password = "password"
}
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
// other clients omitted...
// resource owner password grant client
new Client
{
ClientId = "ro.client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
// AllowedScopes = { "api1" }
AllowedScopes = { "api/user" }
},
new Client
{
ClientId = "winauth",
AllowedGrantTypes = new List<string>{ "windows_auth" },
ClientSecrets =
{
new Secret("secret".Sha256())
},
// AllowedScopes = { "api1" }
AllowedScopes = { "api/user" }
}
};
}
internal static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource { Name = "api1",Scopes = new List<Scope> { new Scope { Name = "api1",
DisplayName = "Full access to API 2"} }, Enabled = true, ApiSecrets = new List<Secret>
{
new Secret("secret".Sha256())
}
},
new ApiResource { Name = "api/user",Scopes = new List<Scope> { new Scope { Name = "api/user",
DisplayName = "Full access to API 2"} }, Enabled = true, ApiSecrets = new List<Secret>
{
new Secret("secret".Sha256())
}
}};
}
public static List<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
}
public class WinAuthGrantValidator : IExtensionGrantValidator
{
private readonly HttpContext httpContext;
public string GrantType => "windows_auth";
public WinAuthGrantValidator(IHttpContextAccessor httpContextAccessor)
{
httpContext = httpContextAccessor.HttpContext;
}
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
// see if windows auth has already been requested and succeeded
var result = await httpContext.AuthenticateAsync("Windows");
if (result?.Principal is WindowsPrincipal wp)
{
context.Result = new GrantValidationResult(wp.Identity.Name, GrantType, wp.Claims);
}
else
{
// trigger windows auth
await httpContext.ChallengeAsync("Windows");
context.Result = new GrantValidationResult { IsError = false, Error = null, Subject = null };
}
}
}
}
web API: Program.cs
public class Program
{
public static void Main(string[] args)
{
var isService = !(Debugger.IsAttached || args.Contains("--console"));
if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
Directory.SetCurrentDirectory(pathToContentRoot);
}
var builder = CreateWebHostBuilder(
args.Where(arg => arg != "--console").ToArray());
var host = builder.Build();
if (isService)
{
// To run the app without the CustomWebHostService change the
// next line to host.RunAsService();
host.RunAsCustomService();
}
else
{
host.Run();
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddEventLog();
})
.ConfigureAppConfiguration((context, config) =>
{
// Configure the app here.
})
.UseStartup<Startup>()
.UseHttpSys(options =>
{
options.AllowSynchronousIO = true;
options.Authentication.Schemes = Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes.Kerberos | Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes.NTLM;
options.Authentication.AllowAnonymous = true;
options.MaxConnections = null;
options.MaxRequestBodySize = 30000000;
options.UrlPrefixes.Add("http://localhost:5000");
});
}
web API UserController.cs
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = "Bearer")]
[ApiController]
public class UserController : ControllerBase
{
// GET api/user
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { User.Identity.Name, User.Identity.AuthenticationType };
}
}
client code:
using (var client = new HttpClient())
{
disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
{
Address = baseUrl,
Policy = { RequireHttps = false }
});
if (disco.IsError)
{
Console.WriteLine(disco.Error);
Console.ReadLine();
return;
}
var httpHandler = new HttpClientHandler
{
UseDefaultCredentials = true,
};
using (var client = new HttpClient())
{
// request token
TokenResponse tokenResponse = await client.RequestTokenAsync(new TokenRequest
{
GrantType = "windows_auth",
Address = disco.TokenEndpoint,
ClientId = "winauth",
ClientSecret = "secret"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
Console.ReadLine();
return;
}
}
It returns unauthorized.
I found a solution to this: I need to configure a TestUser with SubjectId = MYDOMAIN\myusername then it worked.
The error was getting was overly confusing.

Categories