0

I have an SPA (Angular 9) and uses an identity site (.NET Core 3.1 with identity4). Both sites work well, however if the user stops browsing for a long time and tries to navigate again, the identity site gives 503 error (stack overflow error).

This is my config on Angular 9, angular-oauth2-oidc.

// app.component.ts
// ----------------------------

export const authConfig: AuthConfig = {
  issuer: environment.authority,
  redirectUri: environment.redirectUrl,
  clientId: environment.clientId,
  scope: "openid profile offline_access",
  disablePKCE: false,
  responseType: "code",
  requireHttps: false,
  logoutUrl: environment.authority + "/Account/Logout"
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  title = 'app';
  public isAuthenticated: Observable<boolean>;
  public configs = null;

  constructor(
    private ouathService: OAuthService,
    private storage: SessionStorageService
  ) {
    this.ouathService.configure(authConfig);
    this.ouathService.tokenValidationHandler = new JwksValidationHandler();
  }
}

// Authguard.js
// ----------------------------

export class AuthGuard implements CanActivate {
    constructor(..) { }
    canActivate(..): boolean {
        if (this.oauthService.hasValidAccessToken()) {
            console.log("Token validado");
            let token = this.oauthService.getAccessToken();
            //...
            return true;
        } else {
            console.log("Inicia flujo");
            this.oauthService.initImplicitFlow();
            this.oauthService.loadDiscoveryDocumentAndTryLogin();   
        }
    }
}

app.module.ts
----------------------------
@NgModule({
  declarations: [..],
  imports: [
    //..
    OAuthModule.forRoot(),
    RouterModule.forRoot([
      { path: '', component: StartComponent },
      { path: 'logout', component: LoginComponent },
      { path: 'home', component: HomeComponent, canActivate: [AuthGuard, AppGuard] },      
    ])
  ],
  providers: [
    LoaderService,
    { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})

This is my config on Identity site: .NET Core 3.1 and identity4.

// Config
// ----------------------------
public IEnumerable<IdentityResource> GetIdentityResources() {
    return new List<IdentityResource> {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile()
    };
}

public IEnumerable<Client> GetClients() {
    var client = _conf["URL_fronted"];
    return new[] {
        new Client {
            ClientId = "..",
            ClientName = "..",
            AllowedGrantTypes = GrantTypes.Code,
            RequireClientSecret = false,
            RequirePkce = true,
            RedirectUris = { $"{client}" },
            FrontChannelLogoutUri = $"{client}",
            PostLogoutRedirectUris = { $"{client}/logout" },
            AllowOfflineAccess = true,
            AllowedScopes = { "openid","profile", "offline_access" },                    
        }
    };
}

// Startup
// ----------------------------
services.AddIdentity<ApplicationUser, IdentityRole>()
  .AddEntityFrameworkStores<ApplicationDbContext>()
  .AddDefaultTokenProviders();

services.Configure<IISServerOptions>(iis =>
{
  iis.AuthenticationDisplayName = "Windows";
  iis.AutomaticAuthentication = true;
});

var confIdentity = new ConfigIdentity(Configuration);

var builder = services.AddIdentityServer(options =>
{
  options.Events.RaiseErrorEvents = true;
  options.Events.RaiseInformationEvents = true;
  options.Events.RaiseFailureEvents = true;
  options.Events.RaiseSuccessEvents = true;
})
  .AddInMemoryIdentityResources(confIdentity.GetIdentityResources())
  .AddInMemoryClients(confIdentity.GetClients())
  .AddProfileService<IdentityWithAdditionalClaimsProfileService>();
builder.AddDeveloperSigningCredential();
services.AddCors(options =>
{
  options.AddPolicy("CorsPolicy",
      builder => builder.AllowAnyOrigin()
      .AllowAnyMethod()
      .AllowAnyHeader());
});
// IdentityWithAdditionalClaimsProfileService
// -----------------------------------------------

public class IdentityWithAdditionalClaimsProfileService : IProfileService
    {
        private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
        private readonly UserManager<ApplicationUser> _userManager;
        private ILogger<IdentityWithAdditionalClaimsProfileService> _logger;

        private ApplicationDbContext _ctx;
        public IdentityWithAdditionalClaimsProfileService(UserManager<ApplicationUser> userManager,
        IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
        ILogger<IdentityWithAdditionalClaimsProfileService> logger,
        ApplicationDbContext context)
        {
            _userManager = userManager;
            _claimsFactory = claimsFactory;
            _logger = logger;
            _ctx = context;
        }

        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            var sub = context.Subject.GetSubjectId();            
            var user = _ctx.users.Where(..).FirstOrDefault();
            if (user == null)
            {
                throw new Exception("No se encontro usuario para perfilar");
            }

            var claims = new List<Claim>();
            claims.Add(new Claim(ClaimTypes.Name, user.Names));
            claims.Add(new Claim(ClaimTypes.Email, user.Email));

            if (user.RoleId != null)
            {
                var rolGlobal = _ctx.roles.Where(..).FirstOrDefault();
                if (rolGlobal != null)
                {
                    claims.Add(new Claim(ClaimTypes.Role, rolGlobal.AzureGroup));
                }
            }

            context.IssuedClaims = claims;

        }

        public async Task IsActiveAsync(IsActiveContext context)
        {
            context.IsActive = true;
        }
    }

This is the log:

20:41:12 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration/jwks matched to endpoint type Discovery

[20:41:12 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryKeyEndpoint

[20:41:12 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryKeyEndpoint for /.well-known/openid-configuration/jwks

[20:41:12 Debug] IdentityServer4.Endpoints.DiscoveryKeyEndpoint
Start key discovery request

[21:31:23 Debug] IdentityServer4.Hosting.CorsPolicyProvider
CORS request made for path: /.well-known/openid-configuration from origin: http://localhost:4200

[21:31:23 Debug] IdentityServer4.Services.InMemoryCorsPolicyService
Client list checked and origin: http://localhost:4200 is not allowed

[21:31:23 Warning] IdentityServer4.Hosting.CorsPolicyProvider
CorsPolicyService did not allow origin: http://localhost:4200

[21:31:23 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration matched to endpoint type Discovery

[21:31:23 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryEndpoint

[21:31:23 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryEndpoint for /.well-known/openid-configuration

[21:31:23 Debug] IdentityServer4.Endpoints.DiscoveryEndpoint
Start discovery request

[21:31:23 Debug] IdentityServer4.Hosting.CorsPolicyProvider
CORS request made for path: /.well-known/openid-configuration/jwks from origin: http://localhost:4200

[21:31:23 Debug] IdentityServer4.Services.InMemoryCorsPolicyService
Client list checked and origin: http://localhost:4200 is not allowed

[21:31:23 Warning] IdentityServer4.Hosting.CorsPolicyProvider
CorsPolicyService did not allow origin: http://localhost:4200

[21:31:23 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration/jwks matched to endpoint type Discovery

[21:31:23 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryKeyEndpoint

[21:31:23 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryKeyEndpoint for /.well-known/openid-configuration/jwks

[21:31:23 Debug] IdentityServer4.Endpoints.DiscoveryKeyEndpoint
Start key discovery request

Stack overflow.
Jeroen
  • 60,696
  • 40
  • 206
  • 339
Jorge
  • 37
  • 1
  • 7
  • You have circular call somewhere in auth service or the guard – Vega Oct 30 '21 at 02:44
  • Could you also include source code of `IdentityWithAdditionalClaimsProfileService` class in the question? – AndrewSilver Oct 30 '21 at 07:25
  • @AndrewSilver I included the code IdentityWithAdditionalClaimsProfileService. The SPA site, valid the token, but how that is expired, try to renew and redirect to Identity site for new log in, but the site goes down. – Jorge Oct 30 '21 at 14:01
  • A 503 indicates "service not available", often temporarily. The cause can well be a Stack Overflow exception, in any case it'll be a bug in your _csharp_ code, not on your front-end side. Also, as far as I can tell, the culprit is not in code you've posted here. I don't think we'll be able to reproduce your problem with just what you've posted. - Try creating a minimal sample setup and share all the details here, if you can? Good luck! – Jeroen Nov 20 '21 at 17:14

2 Answers2

1

My problem was setting Identityserver as InMemory:

Startup.cs

var builder = services.AddIdentityServer(options =>
{
 //...
})
  .AddInMemoryIdentityResources(confIdentity.GetIdentityResources())
  .AddInMemoryClients(confIdentity.GetClients())

This setting didn't work when a user stopped browsing for a long time. After a while the identity site lost the client's token, and when trying to find it it gave a stackoverflow error

There are two solutions:

1.- Change the configs to use entity framework

services.AddIdentityServer()
        // this adds the operational data from DB (codes, tokens, consents)
        .AddOperationalStore(options =>
        {
            options.ConfigureDbContext = builder =>
                builder.UseSqlServer(connectionString,
                    sql => sql.MigrationsAssembly(migrationsAssembly));

            // this enables automatic token cleanup. this is optional.
            options.EnableTokenCleanup = true;
            options.TokenCleanupInterval = 3600; // interval in seconds (default is 3600)
        });

2.- The client send request to refresh token

app.component.ts

this.ouathService.refreshToken();
Jorge
  • 37
  • 1
  • 7
0
public void ConfigureServices(IServiceCollection services)
{
...
    services.AddSingleton<ICorsPolicyService>((container) => {
            var logger = container.GetRequiredService<ILogger<DefaultCorsPolicyService>>();
            return new DefaultCorsPolicyService(logger)
            {
                 AllowAll = true
            };
        });
...
}
alex
  • 322
  • 3
  • 7