For a project I am running an ASP.NET Core 8 Web API server, Duende Identity Server, and the client side is an Angular 17 app.
But after the user is authenticated in my Duende Identity server should return the user information but after the user get authenticated the browser console returns with requireHttps
error, although I have tried with by keeping it false, true (also added self signed SSL certificates) in both identity server and Angular application but still my browser console returns with the following error:
Error during login callback processing issuer must use HTTPS (with TLS), or config value for property 'requireHttps' must be set to 'false' and allow HTTP (without TLS). auth-callback.component.ts:27:14
ngOnInit auth-callback.component.ts:27
Angular 34
4429 main.ts:5
Webpack 5
I have tried all sorts of things like
dotnet dev-certs https --trust
enabling and disabling https in both identity server and angular application- going through the CORS policy
but so far, all the options I have tried are failing.
Here are my Program.cs
, config.cs
files and from the Angular application auth-service.ts
and auth-callback.ts
files to debug the issue with my code properly
Program.cs
:
using Duende.IdentityServer.Services;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using IdentityModel;
using Duende.IdentityServer.Validation;
using Microsoft.AspNetCore.DataProtection;
using System.IO;
var builder = WebApplication.CreateBuilder(args);
// Configure logging
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
builder.Logging.AddDebug();
// Db context for data protection
builder.Services.AddDbContext<KeyDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
// Configure Data Protection
builder.Services.AddDataProtection()
.PersistKeysToDbContext<KeyDbContext>()
.SetApplicationName(builder.Configuration["DataProtection:ApplicationName"] ?? "DefaultApplicationName");
// Configure DbContext
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Configure IdentityServer
builder.Services.AddScoped<IUserStore, UserStore>();
builder.Services.AddScoped<IProfileService, ProfileService>();
builder.Services.AddScoped<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
builder.Services.AddIdentityServer(options =>
{
// options.EmitStaticAudienceClaim = true;
options.KeyManagement.Enabled = false;
})
.AddInMemoryClients(Config.Clients)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddCorsPolicyService<InMemoryCorsPolicyService>()
.AddDeveloperSigningCredential(persistKey: true, filename: "tempkey.rsa");
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAngularApp", policy =>
{
policy.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors("AllowAngularApp");
app.UseIdentityServer();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
InitializeDatabase(app);
app.Run();
static void InitializeDatabase(IHost app)
{
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
context.Database.Migrate();
// Remove existing data
context.UserClaims.RemoveRange(context.UserClaims);
context.Users.RemoveRange(context.Users);
context.SaveChanges();
if (!context.Users.Any())
{
var users = new List<CustomUser>
{
new CustomUser
{
SubjectId = "1",
Username = "nikhil",
Password = "password",
Claims = new List<UserClaim>
{
new UserClaim { Type = JwtClaimTypes.Name, Value = "Nikhil" },
new UserClaim { Type = JwtClaimTypes.Email, Value = "[email protected]" }
}
},
new CustomUser
{
SubjectId = "2",
Username = "bob",
Password = "password",
Claims = new List<UserClaim>
{
new UserClaim { Type = JwtClaimTypes.Name, Value = "Bob Smith" },
new UserClaim { Type = JwtClaimTypes.Email, Value = "[email protected]" }
}
}
};
context.Users.AddRange(users);
context.SaveChanges();
}
}
}
Config.cs
:
using Duende.IdentityServer.Models;
using Duende.IdentityServer;
using IdentityModel;
using System.Security.Claims;
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource("email", new[] { JwtClaimTypes.Email })
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("api1", "My API", new List<string> { JwtClaimTypes.Name, JwtClaimTypes.Email })
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "ro.client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "openid", "profile", "api1", "email" }
},
new Client
{
ClientId = "mvc",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
ClientSecrets = { new Secret("secret".Sha256()) },
// RedirectUris = { "http://localhost:5183/signin-oidc" },
// PostLogoutRedirectUris = { "http://localhost:5183/signout-callback-oidc" },
RedirectUris = {
"https://localhost:4200",
"https://localhost:4200/signin-oidc",
"https://localhost:4200/silent-refresh.html",
"https://localhost:4200/home",
"https://localhost:4200/auth-callback",
"http://localhost:4200/index.html",
"http://localhost:4200/silent-refresh.html"
}, //angular application addess added
PostLogoutRedirectUris = { "https://localhost:4200/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1",
"offline_access",
"email"
},
AllowOfflineAccess = true,
AccessTokenLifetime = 40,
AbsoluteRefreshTokenLifetime = 60,
AllowedCorsOrigins = new List<string>
{
"https://localhost:4200"
}
}
};
}
auth-service.ts
:
import { Injectable } from '@angular/core';
import { AuthConfig, OAuthService, OAuthErrorEvent } from 'angular-oauth2-oidc';
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor(private oauthService: OAuthService) {
this.configure();
this.subscribeToOAuthEvents();
}
private subscribeToOAuthEvents() {
this.oauthService.events.subscribe(event => {
if (event instanceof OAuthErrorEvent) {
console.error('OAuthErrorEvent:', event);
} else {
console.log('OAuthEvent:', event);
}
});
}
private configure() {
const authConfig: AuthConfig = {
issuer: 'https://localhost:7230',
redirectUri: window.location.origin + '/signin-oidc',
postLogoutRedirectUri: window.location.origin + '/signout-callback-oidc',
clientId: 'mvc',
dummyClientSecret: 'secret', // In angular-oauth2-oidc, use dummyClientSecret instead of clientSecret
responseType: 'code',
scope: 'openid profile email api1 offline_access',
showDebugInformation: true,
oidc: true,
requestAccessToken: true,
silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html',
useSilentRefresh: true,
sessionChecksEnabled: true,
timeoutFactor: 0.01,
silentRefreshTimeout: 10,
disableAtHashCheck: true,
requireHttps: true,
loginUrl: 'https://localhost:7230/connect/authorize', // Set the login URL
logoutUrl: 'https://localhost:7230/connect/endsession', // Set the logout URL
};
this.oauthService.configure(authConfig);
this.oauthService.setupAutomaticSilentRefresh();
this.oauthService.showDebugInformation = true;
this.oauthService.loadDiscoveryDocumentAndTryLogin().then(() => {
console.log('Discovery document loaded and try login completed');
if (this.oauthService.hasValidAccessToken()) {
console.log('Access token is valid');
} else {
console.log('No valid access token found');
}
}).catch(err => {
console.error('Error loading discovery document and trying login', err);
});
}
login() {
console.log('Initiating login flow');
this.oauthService.initCodeFlow();
}
logout() {
console.log('Logging out');
this.oauthService.logOut();
}
get identityClaims() {
return this.oauthService.getIdentityClaims();
}
get accessToken() {
return this.oauthService.getAccessToken();
}
}
auth-callback.ts
:
import { Component, OnInit } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { Router } from '@angular/router';
@Component({
selector: 'app-auth-callback',
template: `<p>Loading...</p>`,
})
export class AuthCallbackComponent implements OnInit {
constructor(private oauthService: OAuthService, private router: Router) {}
ngOnInit() {
this.oauthService.loadDiscoveryDocumentAndTryLogin().then(_ => {
console.log('Discovery document loaded and try login completed');
if (this.oauthService.hasValidAccessToken()) {
console.log('Access token is valid');
console.log('Access Token:', this.oauthService.getAccessToken());
console.log('ID Token:', this.oauthService.getIdToken());
console.log('Identity Claims:', this.oauthService.getIdentityClaims());
this.router.navigate(['/Home']); // Redirect to Home component after successful login
} else {
console.error('No valid access token found');
this.router.navigate(['/']); // Redirect back to login on failure
}
}).catch(err => {
console.error('Error during login callback processing', err);
this.router.navigate(['/']); // Redirect back to login on error
});
}
}
To summarize my problem again my code is failing at
this.oauthService.loadDiscoveryDocumentAndTryLogin()
line in the auth-callback.component.ts
file due to requireHttps
error although I have tried many different things but still have not got any success yet.
app.UseHttpsRedirection();
nor moving its position helped to resolve this issue.http://localhost:4200
(not https) sorequirehttps
should be false right? when in production it should be truerequirehttps
set tofalse
evenif with this configuration i getrequirehttps
error, so i have added ssl keys and crt to solve this issue in both of my applications but it did not solve this issue, i still get requirehttps error with this configuration.