I am building a Blazor Server app on .NET. The app was started using the Visual Studio .NET8 Web template for Blazor server with individual authentication turned on. In the second step, I added an API controller to the app, with the [Authorize] attribute. Here is my program.cs.
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); //added for API
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddBearerToken(IdentityConstants.BearerScheme)
.AddIdentityCookies();
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
//builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
// options.UseSqlServer(connectionString));
builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 26)),
mySqlOptions =>
{
mySqlOptions
.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
}
)
.EnableSensitiveDataLogging(true));//should be scoped as ApplicationDbContext uses the TenantDbContext which is also scoped. By default the service is Singleton
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, EmailSender>();
builder.Services.AddSingleton<RegistrationDataSaver>();
builder.Services.AddAuthorization(); //added for API
builder.Services.AddEndpointsApiExplorer(); //Added for API
builder.Services.AddSwaggerGen(); //Added for API
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
// Inside the ConfigureServices method
builder.Services.AddHttpClient();
builder.Services.AddMudServices();
var app = builder.Build();
app.MapControllers(); //added for API
app.MyMapIdentityApi<ApplicationUser>();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
app.UseSwagger(); //added for API
app.UseSwaggerUI(); //added for API
}
else
{
app.UseExceptionHandler("/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.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
// Add additional endpoints required by the Identity /Account Razor components.
app.MapAdditionalIdentityEndpoints();
app.UseAuthorization();//added for API
app.UseCors(policy =>
policy.WithOrigins("http://localhost:7217", "https://localhost:7217")
.AllowAnyMethod()
.WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization,
"x-custom-header")
.AllowCredentials()
);
app.Run();
}
}
I then use the login endpoint that was part of the Blazor server project to login and obtain a bearer token in return:
private async Task OnValidSubmit()
{
//login to license server and get token
var response = await http.PostAsJsonAsync("https://localhost:44325/login", model); //this uses the injected client
if (!response.IsSuccessStatusCode)
{
Snackbar.Add("License could not be saved.", Severity.Error);
}
AccessTokenResponse tokens;
tokens = response.Content.ReadFromJsonAsync<AccessTokenResponse>().Result;
//create a new client because we will be changing its auth header to connect to license server API.
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken.ToString());
//call API with token set
HttpResponseMessage respon = await httpClient.GetAsync($"https://localhost:44325/api/License/GetLicense/{email}/{product_name}");
if (respon.IsSuccessStatusCode)
{
var jsonResponse = await respon.Content.ReadAsStringAsync(); //here I receive the Login page instead of expected response which is my License object.
var license = JsonSerializer.Deserialize<License>(jsonResponse);
}
}
The question is, why would I be redirected to the Login page when calling the API? I have confirmed that I am receiving the bearer token from the login request. But after I set the authorization header in the next http request and send to the API, instead of receiving my expected data, I am receiving the Blazor login page.