Skip to content
Open
2 changes: 1 addition & 1 deletion src/EntityFramework.Storage/src/Entities/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ public class Client
public string UserCodeType { get; set; }
public int DeviceCodeLifetime { get; set; } = 300;
public bool NonEditable { get; set; }
public bool? CoordinateLifetimeWithUserSession { get; set; }
Comment thread
jhbritton-RSK marked this conversation as resolved.

//Unused Compatibility Properties
public int? CibaLifetime { get; set; }
public int? PollingInterval { get; set; }
public bool? CoordinateLifetimeWithUserSession { get; set; }
public string InitiateLoginUri { get; set; }
public TimeSpan DPoPClockSkew { get; set; }
public int DPoPValidationMode { get; set; }
Expand Down
34 changes: 8 additions & 26 deletions src/EntityFramework.Storage/src/Stores/PersistedGrantStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ public virtual async Task<PersistedGrant> GetAsync(string key)
public async Task<IEnumerable<PersistedGrant>> GetAllAsync(PersistedGrantFilter filter)
{
using var trace = Telemetry.Trace(TelemetryConstants.TraceCategories.Stores, this);
AddFilterTags(trace, filter);

filter.Validate();

Expand Down Expand Up @@ -147,7 +146,6 @@ public virtual async Task RemoveAsync(string key)
public async Task RemoveAllAsync(PersistedGrantFilter filter)
{
using var trace = Telemetry.Trace(TelemetryConstants.TraceCategories.Stores, this);
AddFilterTags(trace, filter);

filter.Validate();

Expand All @@ -168,41 +166,25 @@ public async Task RemoveAllAsync(PersistedGrantFilter filter)
}
}

private void AddFilterTags(ITrace trace, PersistedGrantFilter filter)
{
if (trace == null) return;

if (!String.IsNullOrWhiteSpace(filter.ClientId))
{
trace.AddTag(TelemetryConstants.TagConstants.Client, filter.ClientId);
}
if (!String.IsNullOrWhiteSpace(filter.SubjectId))
{
trace.AddTag(TelemetryConstants.TagConstants.Subject, filter.SubjectId);
}
if (!String.IsNullOrWhiteSpace(filter.Type))
{
trace.AddTag(TelemetryConstants.TagConstants.GrantType, filter.Type);
}
}

private IQueryable<Entities.PersistedGrant> Filter(IQueryable<Entities.PersistedGrant> query, PersistedGrantFilter filter)
{
if (!String.IsNullOrWhiteSpace(filter.ClientId))
var clientIds = filter.ClientIds.Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
if (clientIds.Any())
{
query = query.Where(x => x.ClientId == filter.ClientId);
query = query.Where(x => clientIds.Contains(x.ClientId));
}
if (!String.IsNullOrWhiteSpace(filter.SessionId))
if (!string.IsNullOrWhiteSpace(filter.SessionId))
{
query = query.Where(x => x.SessionId == filter.SessionId);
}
if (!String.IsNullOrWhiteSpace(filter.SubjectId))
if (!string.IsNullOrWhiteSpace(filter.SubjectId))
{
query = query.Where(x => x.SubjectId == filter.SubjectId);
}
if (!String.IsNullOrWhiteSpace(filter.Type))
var types = filter.Types.Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
if (types.Any())
{
query = query.Where(x => x.Type == filter.Type);
query = query.Where(x => types.Contains(x.Type));
}

return query;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,19 @@ public async Task GetAllAsync_Should_Filter(DbContextOptions<PersistedGrantDbCon
SessionId = "s1",
Type = "t3"
})).ToList().Count.Should().Be(0);
(await store.GetAllAsync(new PersistedGrantFilter
{
ClientIds = ["c1", "c3"],
})).ToList().Count.Should().Be(5);
(await store.GetAllAsync(new PersistedGrantFilter
{
Types = ["t2", "t3"],
})).ToList().Count.Should().Be(5);
(await store.GetAllAsync(new PersistedGrantFilter
{
ClientIds = ["c1", "c2"],
Types = ["t1", "t2"],
})).ToList().Count.Should().Be(8);
}
}

Expand Down Expand Up @@ -333,8 +346,7 @@ await store.RemoveAllAsync(new PersistedGrantFilter
});
context.PersistedGrants.Count().Should().Be(9);
}



await PopulateDb();
await using (var context = new PersistedGrantDbContext(options, StoreOptions))
{
Expand Down Expand Up @@ -406,6 +418,43 @@ await store.RemoveAllAsync(new PersistedGrantFilter
context.PersistedGrants.Count().Should().Be(10);
}

await PopulateDb();
await using (var context = new PersistedGrantDbContext(options, StoreOptions))
{
var store = new PersistedGrantStore(context, _telemetry, FakeLogger<PersistedGrantStore>.Create());

await store.RemoveAllAsync(new PersistedGrantFilter
{
ClientIds = ["c2", "c3"],
});
context.PersistedGrants.Count().Should().Be(5);
}

await PopulateDb();
await using (var context = new PersistedGrantDbContext(options, StoreOptions))
{
var store = new PersistedGrantStore(context, _telemetry, FakeLogger<PersistedGrantStore>.Create());

await store.RemoveAllAsync(new PersistedGrantFilter
{
Types = ["t1", "t2"],
});
context.PersistedGrants.Count().Should().Be(2);
}

await PopulateDb();
await using (var context = new PersistedGrantDbContext(options, StoreOptions))
{
var store = new PersistedGrantStore(context, _telemetry, FakeLogger<PersistedGrantStore>.Create());

await store.RemoveAllAsync(new PersistedGrantFilter
{
ClientIds = ["c1", "c3"],
Types = ["t2", "t3"],
});
context.PersistedGrants.Count().Should().Be(7);
}

return;

async Task PopulateDb()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Open.IdentityServer.Models;
using Open.IdentityServer.Services.Default;

namespace Microsoft.Extensions.DependencyInjection;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ public static IIdentityServerBuilder AddCoreServices(this IIdentityServerBuilder

builder.Services.AddCors();
builder.Services.AddTransientDecorator<ICorsPolicyProvider, CorsPolicyProvider>();

builder.Services.AddScoped<IUserSessionEventsService, DefaultUserSessionEventsService>();

return builder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Modified by Rock Solid Knowledge Ltd. Copyright in modifications 2026, Rock Solid Knowledge Ltd.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.


using System;
using Microsoft.AspNetCore.Http;

Expand Down Expand Up @@ -60,4 +60,10 @@ public class AuthenticationOptions
/// If set, will require frame-src CSP headers being emitting on the end session callback endpoint which renders iframes to clients for front-channel signout notification.
/// </summary>
public bool RequireCspFrameSrcForSignout { get; set; } = true;

/// <summary>
/// If set, refresh token lifetimes will be tied to the users' session. This setting can be overridden at the client
/// level.
/// </summary>
public bool CoordinateClientLifetimesWithUserSession { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Modified by Rock Solid Knowledge Ltd. Copyright in modifications 2026, Rock Solid Knowledge Ltd.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.


namespace Open.IdentityServer.Configuration;

/// <summary>
Expand Down Expand Up @@ -54,91 +53,96 @@ public class IdentityServerOptions
/// <value>
/// The endpoints configuration.
/// </value>
public EndpointsOptions Endpoints { get; set; } = new EndpointsOptions();
public EndpointsOptions Endpoints { get; set; } = new();

/// <summary>
/// Gets or sets the discovery endpoint configuration.
/// </summary>
/// <value>
/// The discovery endpoint configuration.
/// </value>
public DiscoveryOptions Discovery { get; set; } = new DiscoveryOptions();
public DiscoveryOptions Discovery { get; set; } = new();

/// <summary>
/// Gets or sets the authentication options.
/// </summary>
/// <value>
/// The authentication options.
/// </value>
public AuthenticationOptions Authentication { get; set; } = new AuthenticationOptions();
public AuthenticationOptions Authentication { get; set; } = new();

/// <summary>
/// Gets or sets the events options.
/// </summary>
/// <value>
/// The events options.
/// </value>
public EventsOptions Events { get; set; } = new EventsOptions();
public EventsOptions Events { get; set; } = new();

/// <summary>
/// Gets or sets the max input length restrictions.
/// </summary>
/// <value>
/// The length restrictions.
/// </value>
public InputLengthRestrictions InputLengthRestrictions { get; set; } = new InputLengthRestrictions();
public InputLengthRestrictions InputLengthRestrictions { get; set; } = new();

/// <summary>
/// Gets or sets the options for the user interaction.
/// </summary>
/// <value>
/// The user interaction options.
/// </value>
public UserInteractionOptions UserInteraction { get; set; } = new UserInteractionOptions();
public UserInteractionOptions UserInteraction { get; set; } = new();

/// <summary>
/// Gets or sets the caching options.
/// </summary>
/// <value>
/// The caching options.
/// </value>
public CachingOptions Caching { get; set; } = new CachingOptions();
public CachingOptions Caching { get; set; } = new();

/// <summary>
/// Gets or sets the cors options.
/// </summary>
/// <value>
/// The cors options.
/// </value>
public CorsOptions Cors { get; set; } = new CorsOptions();
public CorsOptions Cors { get; set; } = new();

/// <summary>
/// Gets or sets the Content Security Policy options.
/// </summary>
public CspOptions Csp { get; set; } = new CspOptions();
public CspOptions Csp { get; set; } = new();

/// <summary>
/// Gets or sets the validation options.
/// </summary>
public ValidationOptions Validation { get; set; } = new ValidationOptions();
public ValidationOptions Validation { get; set; } = new();

/// <summary>
/// Gets or sets the device flow options.
/// </summary>
public DeviceFlowOptions DeviceFlow { get; set; } = new DeviceFlowOptions();
public DeviceFlowOptions DeviceFlow { get; set; } = new();

/// <summary>
/// Gets or sets the logging options
/// </summary>
public LoggingOptions Logging { get; set; } = new LoggingOptions();
public LoggingOptions Logging { get; set; } = new();

/// <summary>
/// Gets or sets the mutual TLS options.
/// </summary>
public MutualTlsOptions MutualTls { get; set; } = new MutualTlsOptions();
public MutualTlsOptions MutualTls { get; set; } = new();

/// <summary>
/// Gets or sets the enable authorise response issuer param option
/// </summary>
public bool EnableAuthorizeResponseIssuerParam { get; set; } = false;

/// <summary>
/// Gets or sets the server-side session options
/// </summary>
public ServerSideSessionsOptions ServerSideSessions { get; set; } = new();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2026, Rock Solid Knowledge Ltd
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

namespace Open.IdentityServer.Configuration;

/// <summary>
/// Server side sessions options.
/// </summary>
public class ServerSideSessionsOptions
{
/// <summary>
/// Specifies if session expiry should trigger back channel logout, this will override any other settings that may
/// cause back channel logout such as AuthenticationOptions.CoordinateClientLifetimesWithUserSession or
/// Client.CoordinateLifetimeWithUserSession.
/// </summary>
public bool ExpiredSessionsTriggerBackchannelLogout { get; set; }
}
24 changes: 18 additions & 6 deletions src/Open.IdentityServer/src/Hosting/IdentityServerMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// Modified by Rock Solid Knowledge Ltd. Copyright in modifications 2026, Rock Solid Knowledge Ltd.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.


using Open.IdentityServer.Events;
using Open.IdentityServer.Extensions;
using Open.IdentityServer.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;
using Open.IdentityServer.Models;

namespace Open.IdentityServer.Hosting;

Expand Down Expand Up @@ -40,14 +41,16 @@ public IdentityServerMiddleware(RequestDelegate next, ILogger<IdentityServerMidd
/// <param name="session">The user session.</param>
/// <param name="events">The event service.</param>
/// <param name="backChannelLogoutService">The service used to send back-channel logout notifications to clients when the user signs out.</param>
/// <param name="userSessionEventsService">The service for handling user session events</param>
/// <param name="telemetryService">The telemetry service</param>
/// <returns>A task that completes when the request has been handled by an IdentityServer endpoint or passed to the next middleware in the pipeline.</returns>
public async Task Invoke(
HttpContext context,
IEndpointRouter router,
IUserSession session,
IEventService events,
IBackChannelLogoutService backChannelLogoutService,
IBackChannelLogoutService backChannelLogoutService,
IUserSessionEventsService userSessionEventsService,
ITelemetryService telemetryService)
{
// this will check the authentication session and from it emit the check session
Expand All @@ -62,13 +65,22 @@ public async Task Invoke(

// this clears our session id cookie so JS clients can detect the user has signed out
await session.RemoveSessionIdCookieAsync();

// notify other services of logout when required
var user = await session.GetUserAsync();
var clientIds = await session.GetClientListAsync();

// back channel logout
var logoutContext = await session.GetLogoutNotificationContext();
if (logoutContext != null)
if (user == null)
{
await backChannelLogoutService.SendLogoutNotificationsAsync(logoutContext);
return;
}

await userSessionEventsService.HandleUserSessionLogout(new UserSessionEventContext
{
SessionId = await session.GetSessionIdAsync(),
SubjectId = user.GetSubjectId(),
ClientIds = clientIds.ToArray(),
});
}
});

Expand Down
2 changes: 2 additions & 0 deletions src/Open.IdentityServer/src/IdentityServerConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ public static class PersistedGrantTypes
public const string UserConsent = "user_consent";
public const string DeviceCode = "device_code";
public const string UserCode = "user_code";

public static readonly string[] PersistedGrantTokenTypes = [AuthorizationCode, ReferenceToken, RefreshToken];
}

public static class UserCodeTypes
Expand Down
Loading