1

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.

5
  • try to remove "app.UseHttpsRedirection();" or move its position in the request pipeline. It will block HTTP requests to identityserver. Commented Jun 17 at 7:16
  • Thank You Tore for response, but neither commenting out app.UseHttpsRedirection(); nor moving its position helped to resolve this issue. Commented Jun 17 at 7:22
  • @ScrappyCoco When you are working on a dev server, its http://localhost:4200 (not https) so requirehttps should be false right? when in production it should be true Commented Jun 17 at 8:22
  • @NarenMurali yes first i have tried with http only with requirehttps set to false evenif with this configuration i get requirehttps 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. Commented Jun 17 at 8:47
  • you should always use HTTPS when you work with Openid-connect, not HTTP, like olicy.WithOrigins("localhost:4200") because, cookies will be blocked by the browsers and the login flow will probably fail. blogged about that here nestenius.se/2023/10/09/debugging-cookie-problems Commented Jun 17 at 8:50

0

Browse other questions tagged or ask your own question.