I am using the swagger gen ui and I am using the following settings and following this GitHub resource.
This seems to be a known issue with swagger according to GitHub, I am using a jwt barrer based token. https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1425
I have setup my swagger gen as follows
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "App Manager - Running Buddies", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
Description = "JWT Authorization header using the Bearer scheme.",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
}, new List<string>()
}
});
});
curl -X GET "https://localhost:44396/api/BmiInformations" -H "accept:
text/plain" -H "Authorization:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTAxOTMyNzQsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0OjQ0Mzk2LyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjQ0Mzk2LyJ9.cbePeT9RJprvTWyQECiUCaoqjc25eFKtf7jh5DwOnU0"
But Still I am getting 401 unauthorised I am using a JWT based token that is valid.
private string BuildToken(LoginModel login) {
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["JwtToken:SecretKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
JwtSecurityToken token;
token = new JwtSecurityToken(_config["JwtToken:Issuer"],
_config["JwtToken:Issuer"], expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private UserModel Authenticate(LoginModel login) {
UserModel user = null;
//var result = await _signInManager.PasswordSignInAsync(, lockoutOnFailure: false);
if (login.Username == "mario" && login.Password == "secret") {
user = new UserModel { UserName = "Mario Rossi", Email = "mario.rossi#domain.com" };
}
return user;
}
This is how am building up my filter.
public class AddAuthHeaderOperationFilter : IOperationFilter {
public void Apply(OpenApiOperation operation, OperationFilterContext context) {
if (operation.Security == null)
operation.Security = new List<OpenApiSecurityRequirement>();
var scheme = new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "bearer" } };
operation.Security.Add(new OpenApiSecurityRequirement {
[scheme] = new List<string>()
});
}
I have added to after my barrer bit. But its still not showing the word barrer
services.AddDbContext<AppManagerDL.AppManagerDBContext>
(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "App Manager - Running Buddies", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme.",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "bearer"
});
c.OperationFilter<AddAuthHeaderOperationFilter>();
Edit 4
Ok So now I have it showing Barrer correctly in the curl but its now saying the signature is invalid even though its getting the correct one from my appsettings.
curl -X GET "https://localhost:44396/api/BmiInformations" -H "accept:
text/plain" -H "Authorization: Bearer
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTAxOTc0MTcsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0OjQ0Mzk2LyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjQ0Mzk2LyJ9.fLWxG1bRX6yCTqFe8XZbgL6Lh1RNcmVFX-636ZvqhNg"
My Settings in start up as as follows.
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext<AppManagerDL.AppManagerDBContext>
(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo { Title = "App Manager - Running Buddies", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
Description = "JWT Authorization header using the Bearer scheme.",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement{
{
new OpenApiSecurityScheme{
Reference = new OpenApiReference{
Id = "Bearer", //The name of the previously defined security scheme.
Type = ReferenceType.SecurityScheme
}
},new List<string>()
}
});
The Exact error I am now getting is.
date: Sat, 23 May 2020 01:04:11 GMT server: Microsoft-IIS/10.0
status: 401 www-authenticate: Bearer error="invalid_token",
error_description="The signature is invalid" x-powered-by: ASP.NET
Try replacing your current security definition with this :
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme.",
Name = "Authorization",
In = ParameterLocation.Header,
Scheme = "bearer",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT"
});
Related
I am trying to authorize a token using swagger. See the image below. My issue is when I type in an invalid token, I don't get any errors or anything.
Below is the API
[Authorize]
[HttpGet("GetUsers")]
public IEnumerable<CountryOutputDto> GetCountriesList()
{
return _countryAppService.GetCountries();
}
Below is are my settings
// Add Authencation
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JWTToken_Auth_API", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 1safsfsdfdfd\"",
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
"JsonWebTokenKeys": {
"ValidateIssuerSigningKey": true,
"IssuerSigningKey": "64A63153-11C1-4919-9133-EFAF99A9B456",
"ValidateIssuer": false,
"ValidIssuer": "https://localhost:8100",
"ValidateAudience": false,
"ValidAudience": "https://localhost:8100",
"RequireExpirationTime": true,
"ValidateLifetime": true
}
I am building a .Net core 6 mvc website which will interact with an API built by an external party. Amongst other things, the user authentication is handled by the API. The API responds with a JWT bearer token once user is authenticated and I need to tie that in to my website to Authorize controller methods.
At this point I call the API and successfully receive the token as expected, however after days of struggling to get [Authorize] to work in the controllers with the token, I am completely lost and hoping for some guidance.
After scrapping multiple iterations of code, this what I currently have.... (excuse the mess)
public async Task<TokenResponse> LoginAsync(string email, string password)
{
var userLogin = new UserLogin
{
Email = email,
Password = password
};
string encoded = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(email + ":" + password));
var client = new RestClient("api location");
var request = new RestRequest();
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Basic " + encoded);
var response = await client.GetAsync(request);
var result = JsonConvert.DeserializeObject<TokenResponse>(response.Content);
return result;
}
public async Task<IActionResult> LoginPostAsync(LoginViewModel viewModel)
{
var tokenResponse = await _userManagementService
.LoginAsync(viewModel.Email, viewModel.Password);
if (!string.IsNullOrEmpty(tokenResponse.access_token))
{
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(tokenResponse.access_token);
var jti = jwtSecurityToken.Claims.First(claim => claim.Type == "jti").Value;
var account_type = jwtSecurityToken.Claims.First(claim => claim.Type == "account_type").Value;
var userId = jwtSecurityToken.Claims.First(claim => claim.Type == "user_id").Value;
var email = jwtSecurityToken.Claims.First(claim => claim.Type == "email").Value;
var iss = jwtSecurityToken.Claims.First(claim => claim.Type == "iss").Value;
string[] userRoles = { "admin", "candidate",};
HttpContext context = new DefaultHttpContext();
var accessToken = tokenResponse.access_token;
//var userClaims = new List<Claim>()
// {
// new Claim("email", email),
// new Claim("account_type", account_type),
// new Claim("jti", jti),
// };
//var userIdentity = new ClaimsIdentity(userClaims, "User Identity");
//var userPrincipal = new ClaimsPrincipal(new[] { userIdentity });
//context.SignInAsync(userPrincipal);
//Response.Cookies.Append(
// Constants.XAccessToken,
// tokenResponse.access_token, new CookieOptions
// {
// Expires = DateTimeOffset.UtcNow.AddMinutes(1),
// HttpOnly = true,
// SameSite = SameSiteMode.Strict
// });
//return new AuthenticateResponse(user, token);
SetJWTCookie(accessToken);
return RedirectToAction("index", "Home", new { area = "CandidateDashboard" });
}
return Unauthorized();
}
Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(config =>
{
config.SaveToken = true;
config.RequireHttpsMetadata = false;
config.TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = false,
ValidateIssuer = true,
ValidIssuer = "issue data",
ValidateIssuerSigningKey = false,
};
config.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["Bearer"];
return Task.CompletedTask;
}
};
});
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Index()
{
return View();
}
This is the what I see in dev console.
--bearer error="invalid_token", error_description="the signature key was not found"
Payload from Bearer
{
"iss": "data here",
"exp": 1647323406,
"nbf": 1647319746,
"iat": 1647319806,
"jti": "e8f297d3-blah blah",
"account_type": "candidate",
"user_id": "2342342342",
"email": "email#email.com"
}
The core problem is that AddJwtBearer by default only trusts token issued by someone it trusts (the issuer) because it needs to verify the signature of the token. You of course want to verify it so a hacker doesn't send fake/forged tokens to your API.
So either you need to add that
.AddJwtBearer(opt =>
{
opt.Authority = "https://issuer.com"
In this way, AddJwtBearer will download the public signing key automatically for you.
Or you need to add the public signing key manually to AddJwtBearer.
see https://devblogs.microsoft.com/dotnet/jwt-validation-and-authorization-in-asp-net-core/
I have this in my Startup.cs in the ConfigureServices:
services.ConfigureJwt(Configuration);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Backend.API", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme (Example: 'Bearer 12345abcdef')",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
I have this in my Startup.cs in the Configure:
app.UseCors("EnableCors");
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
This is my service extension:
public static void ConfigureJwt(this IServiceCollection services, IConfiguration configuration)
{
var settings = new JwtSettings();
settings.Key = configuration["JwtSettings:key"];
settings.Audience = configuration["JwtSettings:audience"];
settings.Issuer = configuration["JwtSettings:issuer"];
settings.MinutesToExpiration = Convert.ToInt32(
configuration["JwtSettings:minutesToExpiration"]);
services.AddSingleton(settings);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "JwtBearer";
options.DefaultChallengeScheme = "JwtBearer";
})
.AddJwtBearer("JwtBearer", jwtBearerOptions =>
{
jwtBearerOptions.RequireHttpsMetadata = false;
jwtBearerOptions.SaveToken = true;
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = settings.Issuer,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(settings.Key)),
ValidAudience = settings.Audience,
ClockSkew = TimeSpan.FromMinutes(
settings.MinutesToExpiration)
};
});
}
and in my Sagger UI login, this is the reply I get:
{
"userId": 1,
"userName": "user",
"firstName": "My FirstName",
"middleName": "A.",
"lastName": "My LastName",
"bearerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqdWFuIiwianRpIjoiNmQwNjQ4ZWMtOGI4YS00YTBkLTlmYmItZTliYWFmNzdmZjI2IiwiVXNlcklkIjoiMSIsIkZpcnN0TmFtZSI6Imp1YW4iLCJNaWRkbGVOYW1lIjoiQS4iLCJMYXN0TmFtZSI6IkRlbGEgQ3J1eiIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6WyJVc2VyIiwiQWRtaW4iXSwiZXhwIjoxNjA2OTc4MjcyLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM2NiIsImF1ZCI6IkdlbmVyYWxUZW1wbGF0ZSJ9.MMnu-suoae7U3QnXJTa9wI2xDUtdDJTtc63KWyd3bZM",
"isAuthenticated": true,
"claims": [
"User",
"Admin"
]
}
Why is it that I still get this error whenever I try to run an endpoint with [Authorize(Roles = "Admin")]
access-control-allow-origin: *
date: Thu03 Dec 2020 06:42:05 GMT
server: Microsoft-IIS/10.0
status: 401
www-authenticate: Bearer
x-powered-by: ASP.NET
This is the endpoint I am trying to run:
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> AddCategory(Category model)
{
var cm = new CategoryManager(context);
var result = await cm.Create(model);
if (result > 0)
{
return StatusCode(StatusCodes.Status201Created, model);
}
return StatusCode(StatusCodes.Status400BadRequest, model);
}
I was having this issue because I was just copy and pasting the "bearerToken" value in the Authorize of swagger. What I should do is copy the "bearerToken" value but add "Bearer " at the beginning.
I'm protecting a Web API with Identity Server 4.
If an external app tries to access it using client credentials but does not pass in the access token, I get, as expected, the Unauthorized response.
The problem here is that the response does not include the WWW-Authenticate header as I was expecting, as stated in the OAuth spec.
Am I missing some config in Identity Server? Or is it something wrong with the Identity Server implementation?
The relevant code parts follow:
Client registration on Identity Server:
new Client()
{
ClientId = "datalookup.clientcredentials",
ClientName = "Data Lookup Client with Client Credentials",
AlwaysIncludeUserClaimsInIdToken = true,
AlwaysSendClientClaims = true,
AllowOfflineAccess = false,
ClientSecrets =
{
new Secret("XXX".Sha256())
},
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes =
{
Scopes.DataLookup.Monitoring,
Scopes.DataLookup.VatNumber
},
ClientClaimsPrefix = "client-",
Claims =
{
new Claim("subs", "1000")
}
}
ApiResource registration on Identity Server:
new ApiResource()
{
Name = "datalookup",
DisplayName = "Data Lookup Web API",
ApiSecrets =
{
new Secret("XXX".Sha256())
},
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Email,
JwtClaimTypes.Profile,
"user-subs"
},
Scopes =
{
new Scope()
{
Name = Scopes.DataLookup.Monitoring,
DisplayName = "Access to the monitoring endpoints",
},
new Scope()
{
Name = Scopes.DataLookup.VatNumber,
DisplayName = "Access to the VAT Number lookup endpoints",
Required = true
}
}
}
Authentication configuration in the Web API:
public void ConfigureServices(IServiceCollection services)
{
(...)
services.AddMvc();
services
.AddAuthorization(
(options) =>
{
options.AddPolicy(
Policies.Monitoring,
(policy) =>
{
policy.RequireScope(Policies.Scopes.Monitoring);
});
options.AddPolicy(
Policies.VatNumber,
(policy) =>
{
policy.RequireScope(Policies.Scopes.VatNumber);
policy.RequireClientSubscription();
});
});
services.AddAuthorizationHandlers();
services
.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(
(options) =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "datalookup";
});
(...)
}
Client accessing the Web API:
using (HttpClient client = new HttpClient())
{
// client.SetBearerToken(accessToken);
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, Constants.WebApiEndpoint))
{
using (HttpResponseMessage response = await client.SendAsync(request).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
ConsoleHelper.WriteErrorLine(response);
return;
}
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
ConsoleHelper.WriteInformationLine(content);
}
}
}
Notice that client.SetBearerToken(accessToken) is commented, so that is why I was expecting the response to include the WWW-Authenticate header.
The whole idea behind this is to implement a feature on a client library to deal with the Http Bearer challenge (as, for example, the Azure KeyVault client library does).
var identity = new GenericIdentity(user.Username, "Token");
var claims = new List<Claim>();
claims.AddRange(identity.Claims);
foreach (RoleType r in roles)
{
claims.Add(new Claim("role", r.ToString()));
}
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, tokenUid));
claims.Add(new Claim(JwtRegisteredClaimNames.Iat,
ServiceHelper.ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64));
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var authToken = new AuthToken();
authToken.TokenValue = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(encodedJwt));
authToken.ExpirationInSeconds = (int)_jwtOptions.ValidFor.TotalSeconds;
return authToken;
The above code is giving me the token taking user credentials as input.
Whenever I try to access the below code using Postman, it is giving me Bearer error ="invalid_token" and 401 unauthorized.
[HttpPost("addStudent")]
[Authorize(Roles = "Director,Student")]
public IActionResult Post([FromBody]Student studentFields)
{
if (s == null)
{
var student = _studentService.CreateStudent(studentFields);
return createResponse(201, new
{
studentInfo = student
});
}
_logger.LogInformation("Student already added:{0}", s);
return createErrorResponse("student already added", 404);
}
In the header, I am giving Authorization = Bearer + token(token generated from above API).
I don't understand why it is giving me an invalid bearer token and 401.
I have seen a lot of examples, whenever a token has been given in the header, the client should able to access the respective API.
In IdentityServer had to add claim "aud" to the jwt Token. In Order to do that enable option.audience that matches ApiResource under .AddJwtBearer("Bearer", options => options.Audience="invoice" and set ApiResource
Reference Link https://identityserver4.readthedocs.io/en/latest/topics/resources.html#refresources
public static readonly IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("invoice", "Invoice API")
{
Scopes = { "invoice.read", "invoice.pay", "manage" }
}
};
}