I am trying to setup authentication for my ASP.NET web api. I have added jwt authentication using Firebase Google as my identity provider. When i am trying to authenticate through swagger ui, i can generate my jwt token, i can add the token ty the "authorize" button in swagger ui, but when i try to call an endpoint requiring auth i get 401. When i do the exact same request from postman i get 200, i just dont understand why!
My setup in program.cs:
Swagger gen setup:
...
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "WorkoutBuddy", Version = "v1" });
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Name = "Authorization",
Description = "Provide JWT token",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type=ReferenceType.SecurityScheme,
Id="Bearer"
}
},
Array.Empty<string>()
}
});
});
...
JWT setup:
...
builder.Services
.AddAuthentication()
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtOptions =>
{
jwtOptions.Authority = builder.Configuration["Auth:ValidIssuer"];
jwtOptions.Audience = builder.Configuration["Auth:Audience"];
jwtOptions.TokenValidationParameters.ValidIssuer = builder.Configuration["Auth:ValidIssuer"];
});
...
Swagger UI setup
...
app.UseExceptionHandler("/error")
.UseSwagger()
.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"/swagger/v1/swagger.json", "WorkoutBuddy Api");
});
app.UseCors(options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
...
Tried configuring swagger ui all possible ways i could find. Nothing seems to work
Here is my full Program.cs
global using WorkoutBuddy.Data;
using Microsoft.EntityFrameworkCore;
using Azure.Identity;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using FirebaseAdmin;
using Google.Apis.Auth.OAuth2;
using WorkoutBuddy.Services;
using WorkoutBuddy.Features;
using Microsoft.OpenApi.Models;
using Microsoft.IdentityModel.Tokens;
using Swashbuckle.AspNetCore.Filters;
var builder = WebApplication.CreateBuilder(args);
var env = builder.Environment;
builder.Configuration
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables()
.AddUserSecrets<Program>();
// var azureCredentials = new DefaultAzureCredential(new DefaultAzureCredentialOptions
// {
// ExcludeEnvironmentCredential = false,
// ExcludeManagedIdentityCredential = false,
// ExcludeSharedTokenCacheCredential = false,
// ExcludeVisualStudioCredential = true,
// ExcludeVisualStudioCodeCredential = true,
// ExcludeAzureCliCredential = false,
// ExcludeAzurePowerShellCredential = true,
// ExcludeInteractiveBrowserCredential = true,
// });
// // Inject keyvault secrets
// var shouldUseKeyVault = builder.Configuration.GetValue<bool>("KeyVault:UseKeyVault", true);
// if (shouldUseKeyVault)
// {
// var keyVaultUrl = builder.Configuration.GetValue<Uri>("KeyVault:Url"); // ?? throw new ArgumentNullException("Missing configuration: KeyVault:Url");
// builder.Configuration.AddAzureKeyVault(keyVaultUrl, azureCredentials);
// }
// Setup EF Core to SQL db connection
builder.Services.AddDbContext<DataContext>(options =>
{
var sqlDbConnectionString = builder.Configuration.GetConnectionString("SQL") ?? throw new ArgumentNullException("Missing SQL connectionstring");
options.UseSqlServer(sqlDbConnectionString, sqlServerOptions =>
{
sqlServerOptions.CommandTimeout(30);
});
});
// setup swagger options
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
// https://www.youtube.com/watch?v=z46lqVOv1hQ&ab_channel=ThumbIKR-ProgrammingExamples
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "WorkoutBuddy", Version = "v1" });
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Name = "Authorization",
Description = "Provide JWT token",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "bearer"
});
// options.AddSecurityRequirement(new OpenApiSecurityRequirement{
// {
// new OpenApiSecurityScheme
// {
// Reference = new OpenApiReference
// {
// Type=ReferenceType.SecurityScheme,
// Id="Bearer"
// }
// },
// Array.Empty<string>()
// }
// });
options.OperationFilter<SecurityRequirementsOperationFilter>();
});
// setup application insight logging
// if (env.EnvironmentName != "Local")
// {
// builder.Services.AddApplicationInsightsTelemetry(options =>
// {
// options.DeveloperMode = false;
// options.ConnectionString = builder.Configuration.GetValue<string>("APPLICATIONINSIGHTS_CONNECTION_STRING") ?? throw new ArgumentNullException("Missing: APPLICATIONINSIGHTS_CONNECTION_STRING");
// });
// }
// setup firebase authentication
// Convert firebase config json to string: https://tools.knowledgewalls.com/json-to-string
var firebaseConfig = builder.Configuration.GetValue<string>("Auth:FirebaseConfig"); // ?? throw new ArgumentNullException("missing firebase config");
FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.FromJson(firebaseConfig)
});
builder.Services
.AddAuthentication()
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtOptions =>
{
jwtOptions.Authority = builder.Configuration["Auth:ValidIssuer"];
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration.GetValue<string>("Auth:ValidIssuer"),
ValidAudience = builder.Configuration.GetValue<string>("Auth:Audience")
};
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Console.WriteLine("Unauthorized request: \n" + context.Exception);
return Task.CompletedTask;
}
};
});
// setup services
builder.Services.AddHttpContextAccessor();
// builder.Services.AddOutputCache(); // can be faulty if multiple instances
builder.Services.AddHttpClient<GoogleJwtProvider, GoogleJwtProvider>(httpClient =>
{
var baseAddress = builder.Configuration["Auth:TokenUri"] ?? throw new ArgumentNullException("Missing: Auth:TokenUri");
var apiKey = builder.Configuration["Auth:FirebaseApiKey"] ?? throw new ArgumentException("Missing firebase api key");
httpClient.BaseAddress = new Uri($"{baseAddress}?key={apiKey}");
});
builder.Services.AddScoped<UserService, UserService>();
builder.Services.AddScoped<ProfileService, ProfileService>();
builder.Services.AddScoped<WorkoutDetailService, WorkoutDetailService>();
builder.Services.AddScoped<ExerciseDetailService, ExerciseDetailService>();
builder.Services.AddScoped<WorkoutService, WorkoutService>();
var app = builder.Build();
switch (args.FirstOrDefault())
{
case "initAndSeedDb":
await app.InitAndSeedDb();
return;
case null:
RunApp(app);
return;
case var arg: throw new ArgumentException($"Unknown command-line argument: {arg}");
}
static void RunApp(WebApplication app)
{
// app.UseExceptionHandler("/error")
// .UseSwagger()
// .UseSwaggerUI(c =>
// {
// c.SwaggerEndpoint($"/swagger/v1/swagger.json", "WorkoutBuddy Api");
// });
app.UseSwagger()
.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"/swagger/v1/swagger.json", "WorkoutBuddy Api");
});
app.UseCors(options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
app.UseHttpsRedirection();
// app.UseOutputCache(); // can be faulty if multiple instances of app running
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
Here is the controller im trying to call:
[Authorize]
[ApiController]
[Route("api/workout")]
public class WorkoutController : CustomControllerBase
{
private readonly ILogger<WorkoutController> _logger;
private readonly WorkoutService _workoutService;
public WorkoutController(
ILogger<WorkoutController> logger,
WorkoutService workoutService)
{
_logger = logger;
_workoutService = workoutService;
}
[HttpGet()]
public async Task<ActionResult<IEnumerable<WorkoutResponse>>> GetAllWorkoutsByProfile()
{
var workoutsResult = await _workoutService.GetWorkoutsForProfile();
return GetDataOrError(
result: workoutsResult,
resolveResponse: (w) => w.Select(e => new WorkoutResponse(e))
);
}
[HttpGet("{id}")]
public async Task<ActionResult<WorkoutResponse>> GetWorkoutById([FromRoute][Required] Guid workoutId)
{
var workoutResult = await _workoutService.GetWorkoutById(workoutId);
return GetDataOrError(
result: workoutResult,
resolveResponse: (w) => new WorkoutResponse(w)
);
}
// delete workout by id
[HttpDelete("{id}")]
public async Task<ActionResult<WorkoutResponse>> DeleteWorkout([FromRoute][Required] Guid workoutId)
{
var workoutResult = await _workoutService.DeleteWorkout(workoutId);
return GetDataOrError(
result: workoutResult,
resolveResponse: (w) => new WorkoutResponse(w)
);
}
// create workoutset by workout id
// create exerciseset by workoutset id
// update exerciseset by workoutset id
}
I have also noticed with the current configuration i have the lockpads looks strange after i authenticate in swagger UI:
After creating a new project from scratch, reusing all my authentication and swagger setup form my original project i finally found a reason.
When i access my swagger ui with http
: http://localhost:5173/swagger/index.html
my swagger fails authentication. Postman works fine when authenticating to my api with http
.
But when i switch to using swagger ui with https
: https://localhost:5272/swagger/index.html
authentication with swagger ui works as expected.