ASP.NET Core API using [ApiController], Parameter always null - c#

I'm using ASP.NET Core to create a REST API. If I use the [APIController] class attribute, the method that uses the POST method with complex parameters always gets null value even though using/without using [FromBody] (I'm using Postman, Raw Json). But if I omit the [APIController] attribute, the parameters on the POST method work normally. I'm using ASP.NET Core 6. What is the effect without using the [APIController] attribute?
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
builder.Services.Configure<IISServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
// Add services to the container.
builder.Services.AddMvc(option =>
{
option.AllowEmptyInputInBodyModelBinding = true;
option.EnableEndpointRouting = true;
option.FormatterMappings.SetMediaTypeMappingForFormat("json", Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"));
}).AddNewtonsoftJson(opt => {
opt.SerializerSettings.DateFormatString = "dd/MM/yyyy HH:mm:ss";
}).AddJsonOptions(options => {
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
Model:
public class BillRequest
{
public string? CompCode { get; set; } = String.Empty;
public string? CustNo { get; set; } = String.Empty;
public string? ID { get; set; } = String.Empty;
public string? Type { get; set; } = String.Empty;
public string? TransDate { get; set; } = String.Empty;
public string? Remark { get; set; } = String.Empty;
public BillRequest()
{
}
}
Controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace FI.Controllers;
[ApiController]
[Route("[Controller]")]
public class VAController : ControllerBase
{
private readonly ILogger<VAController> _logger;
public VAController(ILogger<VAController> logger)
{
_logger = logger;
}
[HttpPost]
//[Authorize(AuthenticationSchemes = VAAuthSchemeConstants.VAAuthScheme)]
[Route("[Action]")]
public BillRequest Bills(BillRequest billReq)
{
try
{
if (ModelState.IsValid)
{
return new BillRequest
{
CompanyCode = "Test - success"
};
}
else
{
return new BillRequest()
{
CompanyCode = "Test - not valid model"
};
}
}
catch(Exception ex)
{
return new BillRequest()
{
CompCode = "Test - error"
};
}
}
}
Postman Payload (Postman Body (Raw, Json)):
{
"CompCode": "Test Comp"
"CustNo": "1235",
"ID": "123123123",
"Type": "01",
"TransDate": "Test Date",
"Remark": "My Remark"
}
Even though I changed the parameter using a string the parameter value is still null.

Related

ASP.NET Core Web API - How to specify required fields in Swagger Documentation

In ASP.NET Core-6 Web API, I am implementing Swagger Documentation.
I have this code.
SwaggerDocOptions:
public class SwaggerDocOptions
{
public string Title { get; set; }
public string Description { get; set; }
public string Organization { get; set; }
public string Email { get; set; }
}
Program.cs
builder.Services.AddSwaggerGen();
builder.Services.AddOptions<SwaggerGenOptions>()
.Configure<IApiVersionDescriptionProvider>((swagger, service) =>
{
foreach (ApiVersionDescription description in service.ApiVersionDescriptions)
{
swagger.SwaggerDoc(description.GroupName, new OpenApiInfo
{
Title = swaggerDocOptions.Title,
Version = description.ApiVersion.ToString(),
Description = swaggerDocOptions.Description,
TermsOfService = new Uri("mysite.org/LICENSE.md"),
Contact = new OpenApiContact
{
Name = swaggerDocOptions.Organization,
Email = swaggerDocOptions.Email
},
License = new OpenApiLicense
{
Name = "MIT",
Url = new Uri("mysite.org/MyApp")
}
});
}
var security = new Dictionary<string, IEnumerable<string>>
{
{"Bearer", new string[0]}
};
swagger.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme.",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT"
});
swagger.OperationFilter<AuthorizeCheckOperationFilter>();
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
swagger.IncludeXmlComments(xmlPath);
});
// Register and Configure API versioning
builder.Services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ReportApiVersions = true;
});
// Register and configure API versioning explorer
builder.Services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
// Configure the HTTP request pipeline.
app.UseSwagger();
app.UseSwaggerUI();
I want the fields that are required to be specified in the Swagger Documentation. That is,
https://localhost:44361/swagger.index.html
However, when I lauched the Application, the required fields are not specified in
https://localhost:44361/swagger.index.html.
How do I achieve this?
Thanks
Add the [Required] attribute to the property of the class:
public class SwaggerDocOptions
{
[Required]
public string Title { get; set; }
public string Description { get; set; }
[Required]
public string Organization { get; set; }
public string Email { get; set; }
}
And don't forget add the [Produces("application/json")] attribute to the API controller. Its purpose is to declare that the controller's actions support a response content type of application/json:
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class TodoController : ControllerBase
{

OData Detla<Entity>Patch not updating

I'm currently trying to hack Odata when updating.
I have the following model:
public class Patient
{
[DataMember]
[JsonPropertyName("id")]
public string Id { get; set; }
[DataMember]
[JsonPropertyName("name")]
public string? Name { get; set; }
[DataMember]
[JsonPropertyName("lastname")]
public string? LastName { get; set; }
[DataMember]
[JsonPropertyName("dateofbirth")]
public DateTime DateBirth { get; set; }
}
On the other hand, I have this controller:
[Route("api/v1/patient")]
public class PatientController:ODataController
{
List<Patient> patients = new List<Patient>()
{
new Patient()
{
Id = "1",
Name = "A name",
LastName = "A lastname"
},
new Patient()
{
Id = "2",
Name = "A second name",
LastName = "A second lastname"
}
};
[EnableQuery]
[HttpGet]
public async Task<IActionResult> GetAll()
{
return Ok(patients);
}
[EnableQuery]
[HttpGet("{id}")]
public async Task<IActionResult> Get([FromODataUri]string id)
{
Patient p = patients.SingleOrDefault(p => p.Id == id) ?? null;
if (p is null)
{
return NotFound();
}
return Ok(p);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]Patient patient)
{
var x = patients;
x.Add(patient);
return Ok(x);
}
[EnableQuery]
[HttpPatch("{id}")]
public async Task<IActionResult> Patch([FromODataUri] string id, [FromBody]Delta<Patient> patient)
{
if (!ModelState.IsValid)
{
throw new System.Web.Http.HttpResponseException(System.Net.HttpStatusCode.BadRequest);
}
Patient patientToEdit = patients.SingleOrDefault(p => p.Id == id) ?? null;
if (patientToEdit is not null)
{
patient.Patch(patientToEdit);
return Ok(patientToEdit);
}
else
{
return NotFound(patient);
}
}
}
However, the entity is not updating when calling with the following:
Json with name only and response not updating
The Program.cs file looks like the following:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHttpContextAccessor();
builder.Services.AddControllers().AddOData(options => options.Select().Expand().Filter().OrderBy().SetMaxTop(100).Count());
builder.Services.AddODataQueryFilter();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.UseRouting();
app.UseODataBatching();
app.UseODataQueryRequest();
app.UseODataRouteDebug();
app.Run();
The Delta patient is showing an empty list of changes when using patient.GetChangedPropertyNames();
Do you know how can I tackle this so I could perform partial patching?

Cannot resolve scoped service 'Application.Interfaces.FacadPattern.IStatisticsFacad' from root provider in .net core5

I am doing Store application with Pro ASP.NET Core 5 MVC. The example is made in ASP.NET Core 5.0.
I get this error while trying to run the program:
Cannot resolve scoped service 'Application.Interfaces.FacadPattern.IStatisticsFacad' from root provider
In this file I use IStatisticsFacad for saving data in the database:
namespace Application.Interfaces.FacadPattern
{
public interface IStatisticsFacad
{
AddStatesticsService AddStatesticsService { get; }
AddStUserService AddStUserService { get; }
AddStatesticsDetailService AddStatesticsDetailService { get; }
}
}
and this is facad class
public class StatisticsFacad: IStatisticsFacad
{
private readonly IDataBaseContext _context;
public StatisticsFacad(IDataBaseContext context
)
{
_context = context;
}
private AddStatesticsService _AddStatesticsService;
public AddStatesticsService AddStatesticsService
{
get
{
return _AddStatesticsService = _AddStatesticsService ?? new AddStatesticsService(_context);
}
}
private AddStUserService _AddStUserService;
public AddStUserService AddStUserService
{
get
{
return _AddStUserService = _AddStUserService ?? new AddStUserService(_context);
}
}
private AddStatesticsDetailService _AddStatesticsDetailService;
public AddStatesticsDetailService AddStatesticsDetailService
{
get
{
return _AddStatesticsDetailService = _AddStatesticsDetailService ?? new AddStatesticsDetailService(_context);
}
}
}
Use in this file ad add in startup file
public class ApplicationVariable
{
private readonly RequestDelegate _next;
private static int TimeOut = 5;
private readonly IStatisticsFacad _statisticFacad;
public ApplicationVariable(RequestDelegate next, IStatisticsFacad statisticFacad)
{
_next = next;
_statisticFacad = statisticFacad;
}
public async Task Invoke(HttpContext httpContext)
{
string UserAgent = httpContext.Request.Headers["User-Agent"].ToString();
string refer = httpContext.Request.Headers["Referer"].ToString();
if (UserAgent == null)
await _next.Invoke(httpContext);
if (Check(httpContext))
{
string rndkey = (new Random().Next(1000, 1000000)).ToString() + "-" + (new Random().Next(10000, 10000000) * DateTime.Now.Millisecond).ToString();
StOnline.set(new On() { key = rndkey, time = DateTime.Now.AddMinutes(TimeOut) });
httpContext.Response.Cookies.Append("OnlineUser", rndkey, new CookieOptions
{
Expires = DateTime.Now.AddMinutes(TimeOut)
});
httpContext.Session.SetString("OnlineUser", rndkey);
var q = _statisticFacad.AddStatesticsService.Execute().IsSuccess;
DateTime.Now.Date).SingleOrDefault();
var uaParser = Parser.GetDefault();
UAParser.ClientInfo UserInfo = uaParser.Parse(UserAgent);
if (q)
{
StUserDto stUser = new StUserDto()
{
OS = UserInfo.OS.Family,
Browser = UserInfo.UA.Family,
KeyId = rndkey
};
var t = _statisticFacad.AddStUserService.Execute(stUser).IsSuccess;
var IpAdd = httpContext.Connection.RemoteIpAddress.ToString() != null ?
httpContext.Connection.RemoteIpAddress.ToString() :
httpContext.Connection.LocalIpAddress.ToString();
string Contry1 = new GetLocation().Get(IpAdd).country;
StStatDto stDetail = new StStatDto()
{
CountryName = Contry1,
Ip = IpAdd,
Refferer = httpContext.Request.Headers["Referer"].ToString(),
Url = httpContext.Request.Host + "/" + httpContext.Request.Path,
Key = rndkey,
};
var sd = _statisticFacad.AddStatesticsDetailService.Execute(stDetail).IsSuccess;
}
else
{
}
}
else
{
string key = GetKey(httpContext);
StOnline.Update(key, TimeOut);
httpContext.Response.Cookies.Delete("OnlineUser", new CookieOptions
{
Expires = DateTime.Now.AddMinutes(TimeOut)
});
httpContext.Session.Remove("OnlineUser");
httpContext.Session.SetString("OnlineUser", key);
}
await _next.Invoke(httpContext);
}
}
and startup file
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IDataBaseContext, DataBaseContext>();
services.AddSingleton<IRecaptchaExtension, RecaptchaExtension>();
services.AddHttpClient();
services.AddDbContext<DataBaseContext>
(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMemoryCache();
services.AddSession();
services.AddScoped<IStatisticsFacad, StatisticsFacad>();
services.AddControllersWithViews();
services.AddIdentity<User, Role>()
.AddEntityFrameworkStores<DataBaseContext>()
.AddDefaultTokenProviders()
.AddRoles<Role>()
.AddErrorDescriber<CustomIdentityError>()
.AddPasswordValidator<MyPasswordValidator>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddFluentValidation();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseMiddleware<ApplicationVariable>();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "areas",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
How to solve this problem?

Primary Key exception inserting into AspNetUsers table with Identity Framework Core 2

I just setup a basic integration to Identity Framework Core 2.0.1 in my app. I've configured IdentityUser to use INT instead of STRING as its primary key as per this article (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-primary-key-configuration?tabs=aspnetcore2x).
I'm using code first with a blank database created via an initial migration.
The problem I'm running into is that when I create a user (I'm using postman), I get back the following exception:
Cannot insert explicit value for identity column in table 'AspNetUsers' when IDENTITY_INSERT is set to OFF.
I checked the value of appUser.Id when it is passed to UserManager.CreateAsync and it is definitely = 0 (which is the CLR default value for an Int property)
If I configure AppUser to use a String as a primary key (the default settings), the Id column is not set as an identity column and I get no error - the user is created without a problem with a GUID as the ID generated internally by the Identity framework.
From postman:
{
"email":"someemail#comcast.net",
"password":"Password123",
"firstName":"Alex",
"lastName":"Florin",
"userName":"aflorin"
}
AppUser.cs
using Microsoft.AspNetCore.Identity;
namespace Driver.Models.Entities
{
public class AppUser : IdentityUser<int>
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
AppRole.cs
using Microsoft.AspNetCore.Identity;
namespace Driver.Models.Entities
{
public class AppRole : IdentityRole<int> {
}
}
IAppUserRepository.cs
using System.Threading.Tasks;
namespace Driver.Repositories
{
public interface IAppUserRepository<AppUser>
{
Task Create(AppUser appUser, string password);
}
}
AppUserRepository.cs
using Driver.DBContext;
using Driver.Models.Entities;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;
namespace Driver.Repositories
{
public class AppUserRepository : IAppUserRepository<AppUser>
{
private UserManager<AppUser> _userManager;
protected DriverDbContext _dbContext;
public AppUserRepository(UserManager<AppUser> userManager, DriverDbContext dbContext)
{
_userManager = userManager;
_dbContext = dbContext;
}
public async Task Create(AppUser appUser, string password)
{
var result = await _userManager.CreateAsync(appUser, password);
//ToDo: if (!result.Succeeded) { }
await _dbContext.SaveChangesAsync();
}
}
}
AccountsController.cs
using Driver.Models.Entities;
using Driver.ViewModels.Identity;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Driver.Repositories;
namespace Driver.Controllers
{
[Produces("application/json")]
[Route("api/[controller]")]
public class AccountsController : Controller
{
private IAppUserRepository<AppUser> _appUserRepository;
public AccountsController(UserManager<AppUser> userManager, IAppUserRepository<AppUser> appUserRepository)
{
_appUserRepository = appUserRepository;
}
[HttpPost]
public async Task<IActionResult> Create([FromBody]RegistrationViewModel registrationVM)
{
if (registrationVM == null)
{
return BadRequest();
}
var appUser = Mapper.Map<AppUser>(registrationVM);
await _appUserRepository.Create(appUser, registrationVM.Password);
return CreatedAtAction("Create", new { id = appUser.Id }, Mapper.Map<RegistrationViewModel>(appUser));
}
}
}
RegistrationViewModel.cs
namespace Driver.ViewModels.Identity
{
public class RegistrationViewModel
{
public string Email { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
}
}
ConfigureServices from Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DriverDbContext>(options => options.UseSqlServer(_config.GetConnectionString("DriverDBConnection")));
services.AddSingleton<IJwtFactory, JwtFactory>();
services.TryAddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddIdentity<AppUser, IdentityRole<int>>()
.AddEntityFrameworkStores<DriverDbContext>()
.AddDefaultTokenProviders();
var settings = _config.GetSection("Authentication").Get<AuthenticationAppSettings>();
// Configure JwtIssuerOptions
services.Configure((Models.JwtIssuerOptions options) =>
{
options.Issuer = settings.JwtIssuerOptions.Issuer;
options.Audience = settings.JwtIssuerOptions.Audience;
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
// Specify the validation parameters to dictate how we want received tokens validated
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = settings.JwtIssuerOptions.Issuer,
ValidateAudience = true,
ValidAudience = settings.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 = settings.JwtIssuerOptions.Issuer;
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
});
// Create an authorization claim policy to guard API controllers and actions
services.AddAuthorization(options =>
{
options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
});
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = settings.Password.RequiredDigit;
options.Password.RequiredLength = settings.Password.RequiredLength;
options.Password.RequireNonAlphanumeric = settings.Password.RequireNonAlphanumeric;
options.Password.RequireUppercase = settings.Password.RequireUppercase;
options.Password.RequireLowercase = settings.Password.RequireLowercase;
options.Password.RequiredUniqueChars = settings.Password.RequiredUniqueChars;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(settings.Lockout.DefaultLockoutTimeSpan);
options.Lockout.MaxFailedAccessAttempts = settings.Lockout.MaxFailedAccessAttempts;
options.Lockout.AllowedForNewUsers = settings.Lockout.AllowedForNewUsers;
// User settings
options.User.RequireUniqueEmail = settings.User.RequireUniqueEmail;
});
services.AddMvc().AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
services.AddAutoMapper(typeof(Startup));
services.AddScoped<ICompanyRepository, CompanyRepository>();
services.AddScoped<ILookupRepository, LookupRepository>();
services.AddScoped<IManagerRepository, ManagerRepository>();
services.AddScoped<IAppUserRepository<AppUser>, AppUserRepository>();
}
The class declaration line for my DbContext (I'm not doing any custom configuration on the AppUser entity):
public class DriverDbContext : IdentityDbContext<AppUser, AppRole, int>

Use JWT (Authorization: Bearer) in Swagger in ASP.NET Core

I'm creating a REST api in ASP.NET Core 1.0. I was using Swagger to test but now I added JWT authorization for some routes. (with UseJwtBearerAuthentication)
Is it possible to modify the header of the Swagger requests so the routes with the [Authorize] attribute can be tested?
I struggled with the same problem and found a working solution in this blogpost:
http://blog.sluijsveld.com/28/01/2016/CustomSwaggerUIField
It comes down to adding this in your configurationoptions
services.ConfigureSwaggerGen(options =>
{
options.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
});
and the code for the operationfilter
public class AuthorizationHeaderParameterOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors;
var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);
if (isAuthorized && !allowAnonymous)
{
if (operation.Parameters == null)
operation.Parameters = new List<IParameter>();
operation.Parameters.Add(new NonBodyParameter
{
Name = "Authorization",
In = "header",
Description = "access token",
Required = true,
Type = "string"
});
}
}
}
Then you will see an extra Authorization TextBox in your swagger where you can add your token in the format 'Bearer {jwttoken}' and you should be authorized in your swagger requests.
Currently Swagger has functionality for authentication with JWT-token and can automatically add token into header (I'm using Swashbuckle.AspNetCore 1.1.0).
The following code should help achieve this.
In the Startup.ConfigureServices():
services.AddSwaggerGen(c =>
{
// Your custom configuration
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
c.DescribeAllEnumsAsStrings();
// JWT-token authentication by password
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "password",
TokenUrl = Path.Combine(HostingEnvironment.WebRootPath, "/token"),
// Optional scopes
//Scopes = new Dictionary<string, string>
//{
// { "api-name", "my api" },
//}
});
});
Check and configure TokenUrl if your endpoint is different.
In the Startup.Configure():
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
// Provide client ID, client secret, realm and application name (if need)
// Swashbuckle.AspNetCore 4.0.1
c.OAuthClientId("swagger-ui");
c.OAuthClientSecret("swagger-ui-secret");
c.OAuthRealm("swagger-ui-realm");
c.OAuthAppName("Swagger UI");
// Swashbuckle.AspNetCore 1.1.0
// c.ConfigureOAuth2("swagger-ui", "swagger-ui-secret", "swagger-ui-realm", "Swagger UI");
});
If your endpoint for authentication by token follows the OAuth2 standard, all should work. But just in case, I have added sample of this endpoint:
public class AccountController : Controller
{
[ProducesResponseType(typeof(AccessTokens), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.Unauthorized)]
[HttpPost("/token")]
public async Task<IActionResult> Token([FromForm] LoginModel loginModel)
{
switch (loginModel.grant_type)
{
case "password":
var accessTokens = // Authentication logic
if (accessTokens == null)
return BadRequest("Invalid user name or password.");
return new ObjectResult(accessTokens);
case "refresh_token":
var accessTokens = // Refresh token logic
if (accessTokens == null)
return Unauthorized();
return new ObjectResult(accessTokens);
default:
return BadRequest("Unsupported grant type");
}
}
}
public class LoginModel
{
[Required]
public string grant_type { get; set; }
public string username { get; set; }
public string password { get; set; }
public string refresh_token { get; set; }
// Optional
//public string scope { get; set; }
}
public class AccessTokens
{
public string access_token { get; set; }
public string refresh_token { get; set; }
public string token_type { get; set; }
public int expires_in { get; set; }
}
To expand on HansVG answer which worked for me (thanks) and since I don't have enough contribution points I can't answer emseetea question directly. Once you have the Authorization textbox you will need to call the endpoint that generate the token which will be outside your must [Authorize] area of endpoints.
Once you have called that endpoint to generate the token from the endpoint you can copy it out of the results for that endpoint. Then you have the token to use in your other areas that are must [Authorize]. Just paste it in the textbox. Make sure, as HansVG mentioned, to add it in the correct format, which needs to include "bearer ". Format = "bearer {token}".
Thanks to the Pavel K.'s answer, this is the way I finally resolved this issue in ASP.NET Core 2.2 with Swagger 4.0.1.
In the Startup.cs ConfigureServices():
public void ConfigureServices(IServiceCollection services)
{
.
.
.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "...", Version = "v1" });
.
.
.
c.AddSecurityDefinition("Bearer", new OAuth2Scheme
{
Flow = "password",
TokenUrl = "/token"
});
// It must be here so the Swagger UI works correctly (Swashbuckle.AspNetCore.SwaggerUI, Version=4.0.1.0)
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
{
{"Bearer", new string[] { }}
});
});
.
.
.
}
In the Startup.cs Configure():
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
.
.
.
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "...");
// Provide client ID, client secret, realm and application name (if need)
c.OAuthClientId("...");
c.OAuthClientSecret("...");
c.OAuthRealm("...");
c.OAuthAppName("...");
});
.
.
.
}
And here is how I made an endpoint to give out a JWT token:
[ApiController, Route("[controller]")]
public class TokenController : ControllerBase
{
[HttpPost, AllowAnonymous]
public async Task<ActionResult<AccessTokensResponse>> RequestToken([FromForm]LoginRequest request)
{
var claims = await ValidateCredentialAndGenerateClaims(request);
var now = DateTime.UtcNow;
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_setting.SecurityKey));
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _setting.Issuer,
audience: _setting.Audience,
claims: claims,
notBefore: now,
expires: now.AddMinutes(_setting.ValidDurationInMinute),
signingCredentials: signingCredentials);
return Ok(new AccessTokensResponse(token));
}
}
All your rules and logic on validating user name and password (and/or client_id and clinet_secret) will be in ValidateCredentialAndGenerateClaims().
If you just wonder, these are my request and response models:
/// <summary>
/// Encapsulates fields for login request.
/// </summary>
/// <remarks>
/// See: https://www.oauth.com/oauth2-servers/access-tokens/
/// </remarks>
public class LoginRequest
{
[Required]
public string grant_type { get; set; }
public string username { get; set; }
public string password { get; set; }
public string refresh_token { get; set; }
public string scope { get; set; }
public string client_id { get; set; }
public string client_secret { get; set; }
}
/// <summary>
/// JWT successful response.
/// </summary>
/// <remarks>
/// See: https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
/// </remarks>
public class AccessTokensResponse
{
/// <summary>
/// Initializes a new instance of <seealso cref="AccessTokensResponse"/>.
/// </summary>
/// <param name="securityToken"></param>
public AccessTokensResponse(JwtSecurityToken securityToken)
{
access_token = new JwtSecurityTokenHandler().WriteToken(securityToken);
token_type = "Bearer";
expires_in = Math.Truncate((securityToken.ValidTo - DateTime.UtcNow).TotalSeconds);
}
public string access_token { get; set; }
public string refresh_token { get; set; }
public string token_type { get; set; }
public double expires_in { get; set; }
}
You may add any additional header with API call by using this swagger configuration
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Version = "v1",
Title = "Core API",
Description = "ASP.NET Core API",
TermsOfService = "None",
Contact = new Contact
{
Name = "Raj Kumar",
Email = ""
},
License = new License
{
Name = "Demo"
}
});
c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
{
{"Bearer",new string[]{}}
});
});
I would also check for AuthorizeAttribute.
var filterDescriptor = context.ApiDescription.ActionDescriptor.FilterDescriptors;
var hasAuthorizedFilter = filterDescriptor.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
var allowAnonymous = filterDescriptor.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);
var hasAuthorizedAttribute = context.MethodInfo.ReflectedType?.CustomAttributes.First().AttributeType ==
typeof(AuthorizeAttribute);
if ((hasAuthorizedFilter || hasAuthorizedAttribute) && !allowAnonymous)
{
var oAuthScheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }
};
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[ oAuthScheme ] = new List<string>()
}
};
}
Controller Action:
[Authorize(Policy = AppConfiguration.PermissionReadWrite)]
[Route("api/[controller]")]
[ApiController]
public class FooController : ControllerBase
{
...
}
I integrate swagger with firebase
Configure Swagger Authentication with Firebase (google) in .Net core

Categories