diff --git a/samples/JS6.EF/wwwroot/index.html b/samples/JS6.EF/wwwroot/index.html index 43e53781..b57d2bf7 100644 --- a/samples/JS6.EF/wwwroot/index.html +++ b/samples/JS6.EF/wwwroot/index.html @@ -9,7 +9,7 @@
diff --git a/src/Duende.Bff.EntityFramework/Store/UserSessionStore.cs b/src/Duende.Bff.EntityFramework/Store/UserSessionStore.cs index 09ce1554..5e88b201 100644 --- a/src/Duende.Bff.EntityFramework/Store/UserSessionStore.cs +++ b/src/Duende.Bff.EntityFramework/Store/UserSessionStore.cs @@ -40,6 +40,8 @@ public UserSessionStore(IOptions options, ISessionDbConte /// public async Task CreateUserSessionAsync(UserSession session, CancellationToken cancellationToken) { + _logger.LogDebug("Creating user session record in store for sub {sub} sid {sid}", session.SubjectId, session.SessionId); + var item = new UserSessionEntity() { ApplicationName = _applicationDiscriminator @@ -65,6 +67,8 @@ public async Task DeleteUserSessionAsync(string key, CancellationToken cancellat if (item != null) { + _logger.LogDebug("Deleting user session record in store for sub {sub} sid {sid}", item.SubjectId, item.SessionId); + _sessionDbContext.UserSessions.Remove(item); try { @@ -114,6 +118,8 @@ public async Task DeleteUserSessionsAsync(UserSessionsFilter filter, Cancellatio items = items.Where(x => x.SessionId == filter.SessionId).ToArray(); } + _logger.LogDebug("Deleting {count} user session(s) from store for sub {sub} sid {sid}", items.Length, filter.SubjectId, filter.SessionId); + _sessionDbContext.UserSessions.RemoveRange(items); try @@ -143,6 +149,8 @@ public async Task GetUserSessionAsync(string key, CancellationToken UserSession result = null; if (item != null) { + _logger.LogDebug("Getting user session record from store for sub {sub} sid {sid}", item.SubjectId, item.SessionId); + result = new UserSession(); item.CopyTo(result); } @@ -179,12 +187,16 @@ public async Task> GetUserSessionsAsync(UserSes items = items.Where(x => x.SessionId == filter.SessionId).ToArray(); } - return items.Select(x => + var results = items.Select(x => { var item = new UserSession(); x.CopyTo(item); return item; }).ToArray(); + + _logger.LogDebug("Getting {count} user session(s) from store for sub {sub} sid {sid}", results.Length, filter.SubjectId, filter.SessionId); + + return results; } /// @@ -194,6 +206,8 @@ public async Task UpdateUserSessionAsync(string key, UserSessionUpdate session, var item = items.SingleOrDefault(x => x.Key == key && x.ApplicationName == _applicationDiscriminator); if (item != null) { + _logger.LogDebug("Updating user session record in store for sub {sub} sid {sid}", item.SubjectId, item.SessionId); + session.CopyTo(item); await _sessionDbContext.SaveChangesAsync(cancellationToken); } diff --git a/src/Duende.Bff/EndpointServices/BackchannelLogout/DefaultBackchannelLogoutService.cs b/src/Duende.Bff/EndpointServices/BackchannelLogout/DefaultBackchannelLogoutService.cs index dbebc532..7a05c0fd 100644 --- a/src/Duende.Bff/EndpointServices/BackchannelLogout/DefaultBackchannelLogoutService.cs +++ b/src/Duende.Bff/EndpointServices/BackchannelLogout/DefaultBackchannelLogoutService.cs @@ -66,6 +66,8 @@ public DefaultBackchannelLogoutService( /// public virtual async Task ProcessRequestAsync(HttpContext context) { + Logger.LogDebug("Processing back-channel logout request"); + context.Response.Headers.Add("Cache-Control", "no-cache, no-store"); context.Response.Headers.Add("Pragma", "no-cache"); @@ -120,8 +122,13 @@ await UserSession.RevokeSessionsAsync(new UserSessionsFilter var claims = await ValidateJwt(logoutToken); if (claims == null) { + Logger.LogDebug("No claims in back-channel JWT"); return null; } + else + { + Logger.LogTrace("Claims found in back-channel JWT {claims}", claims.Claims); + } if (claims.FindFirst("sub") == null && claims.FindFirst("sid") == null) { @@ -174,6 +181,7 @@ await UserSession.RevokeSessionsAsync(new UserSessionsFilter var result = handler.ValidateToken(jwt, parameters); if (result.IsValid) { + Logger.LogDebug("Back-channel JWT validation successful"); return result.ClaimsIdentity; } diff --git a/src/Duende.Bff/EndpointServices/Login/DefaultLoginService.cs b/src/Duende.Bff/EndpointServices/Login/DefaultLoginService.cs index 10710671..3e55a009 100644 --- a/src/Duende.Bff/EndpointServices/Login/DefaultLoginService.cs +++ b/src/Duende.Bff/EndpointServices/Login/DefaultLoginService.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Linq; @@ -19,26 +20,35 @@ public class DefaultLoginService : ILoginService /// The BFF options /// protected readonly BffOptions Options; - + /// /// The return URL validator /// protected readonly IReturnUrlValidator ReturnUrlValidator; + + /// + /// The logger + /// + protected readonly ILogger Logger; /// /// ctor /// /// /// - public DefaultLoginService(IOptions options, IReturnUrlValidator returnUrlValidator) + /// + public DefaultLoginService(IOptions options, IReturnUrlValidator returnUrlValidator, ILogger logger) { Options = options.Value; ReturnUrlValidator = returnUrlValidator; + Logger = logger; } /// public virtual async Task ProcessRequestAsync(HttpContext context) { + Logger.LogDebug("Processing login request"); + context.CheckForBffMiddleware(Options); var returnUrl = context.Request.Query[Constants.RequestParameters.ReturnUrl].FirstOrDefault(); @@ -68,6 +78,8 @@ public virtual async Task ProcessRequestAsync(HttpContext context) RedirectUri = returnUrl }; + Logger.LogDebug("Login endpoint triggering Challenge with returnUrl {returnUrl}", returnUrl); + await context.ChallengeAsync(props); } } \ No newline at end of file diff --git a/src/Duende.Bff/EndpointServices/Logout/DefaultLogoutService.cs b/src/Duende.Bff/EndpointServices/Logout/DefaultLogoutService.cs index 352eae2b..2043b1e4 100644 --- a/src/Duende.Bff/EndpointServices/Logout/DefaultLogoutService.cs +++ b/src/Duende.Bff/EndpointServices/Logout/DefaultLogoutService.cs @@ -4,6 +4,7 @@ using IdentityModel; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Linq; @@ -30,6 +31,11 @@ public class DefaultLogoutService : ILogoutService /// The return URL validator /// protected readonly IReturnUrlValidator ReturnUrlValidator; + + /// + /// The logger + /// + protected readonly ILogger Logger; /// /// Ctor @@ -37,16 +43,23 @@ public class DefaultLogoutService : ILogoutService /// /// /// - public DefaultLogoutService(IOptions options, IAuthenticationSchemeProvider authenticationAuthenticationSchemeProviderProvider, IReturnUrlValidator returnUrlValidator) + /// + public DefaultLogoutService(IOptions options, + IAuthenticationSchemeProvider authenticationAuthenticationSchemeProviderProvider, + IReturnUrlValidator returnUrlValidator, + ILogger logger) { Options = options.Value; AuthenticationSchemeProvider = authenticationAuthenticationSchemeProviderProvider; ReturnUrlValidator = returnUrlValidator; + Logger = logger; } /// public virtual async Task ProcessRequestAsync(HttpContext context) { + Logger.LogDebug("Processing logout request"); + context.CheckForBffMiddleware(Options); var result = await context.AuthenticateAsync(); @@ -66,12 +79,7 @@ public virtual async Task ProcessRequestAsync(HttpContext context) } } - // get rid of local cookie first - var signInScheme = await AuthenticationSchemeProvider.GetDefaultSignInSchemeAsync(); - await context.SignOutAsync(signInScheme?.Name); - var returnUrl = context.Request.Query[Constants.RequestParameters.ReturnUrl].FirstOrDefault(); - if (!string.IsNullOrWhiteSpace(returnUrl)) { if (!await ReturnUrlValidator.IsValidAsync(returnUrl)) @@ -80,6 +88,10 @@ public virtual async Task ProcessRequestAsync(HttpContext context) } } + // get rid of local cookie first + var signInScheme = await AuthenticationSchemeProvider.GetDefaultSignInSchemeAsync(); + await context.SignOutAsync(signInScheme?.Name); + if (String.IsNullOrWhiteSpace(returnUrl)) { if (context.Request.PathBase.HasValue) @@ -97,6 +109,8 @@ public virtual async Task ProcessRequestAsync(HttpContext context) RedirectUri = returnUrl }; + Logger.LogDebug("Logout endpoint triggering SignOut with returnUrl {returnUrl}", returnUrl); + // trigger idp logout await context.SignOutAsync(props); } diff --git a/src/Duende.Bff/EndpointServices/SilentLogin/DefaultSilentLoginCallbackService.cs b/src/Duende.Bff/EndpointServices/SilentLogin/DefaultSilentLoginCallbackService.cs index 010a8e99..e29f274d 100644 --- a/src/Duende.Bff/EndpointServices/SilentLogin/DefaultSilentLoginCallbackService.cs +++ b/src/Duende.Bff/EndpointServices/SilentLogin/DefaultSilentLoginCallbackService.cs @@ -4,6 +4,7 @@ using IdentityModel; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Text; using System.Threading.Tasks; @@ -15,21 +16,33 @@ namespace Duende.Bff; /// public class DefaultSilentLoginCallbackService : ISilentLoginCallbackService { - private readonly BffOptions _options; + /// + /// The BFF options + /// + protected readonly BffOptions Options; + + /// + /// The logger + /// + protected readonly ILogger Logger; /// /// ctor /// /// - public DefaultSilentLoginCallbackService(IOptions options) + /// + public DefaultSilentLoginCallbackService(IOptions options, ILogger logger) { - _options = options.Value; + Options = options.Value; + Logger = logger; } /// - public async Task ProcessRequestAsync(HttpContext context) + public virtual async Task ProcessRequestAsync(HttpContext context) { - context.CheckForBffMiddleware(_options); + Logger.LogDebug("Processing silent login callback request"); + + context.CheckForBffMiddleware(Options); var result = (await context.AuthenticateAsync()).Succeeded ? "true" : "false"; var json = $"{{source:'bff-silent-login', isLoggedIn:{result}}}"; @@ -46,6 +59,8 @@ public async Task ProcessRequestAsync(HttpContext context) context.Response.Headers["Cache-Control"] = "no-store, no-cache, max-age=0"; context.Response.Headers["Pragma"] = "no-cache"; + Logger.LogDebug("Silent login endpoint rendering HTML with JS postMessage to origin {origin} with isLoggedIn {isLoggedIn}", origin, result); + await context.Response.WriteAsync(html, Encoding.UTF8); } } \ No newline at end of file diff --git a/src/Duende.Bff/EndpointServices/SilentLogin/DefaultSilentLoginService.cs b/src/Duende.Bff/EndpointServices/SilentLogin/DefaultSilentLoginService.cs index be5eb216..514827b1 100644 --- a/src/Duende.Bff/EndpointServices/SilentLogin/DefaultSilentLoginService.cs +++ b/src/Duende.Bff/EndpointServices/SilentLogin/DefaultSilentLoginService.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Threading.Tasks; @@ -13,24 +14,36 @@ namespace Duende.Bff; /// public class DefaultSilentLoginService : ISilentLoginService { - private readonly BffOptions _options; + /// + /// The BFF options + /// + protected readonly BffOptions Options; + + /// + /// The logger + /// + protected readonly ILogger Logger; /// /// ctor /// /// - public DefaultSilentLoginService(IOptions options) + /// + public DefaultSilentLoginService(IOptions options, ILogger logger) { - _options = options.Value; + Options = options.Value; + Logger = logger; } /// - public async Task ProcessRequestAsync(HttpContext context) + public virtual async Task ProcessRequestAsync(HttpContext context) { - context.CheckForBffMiddleware(_options); + Logger.LogDebug("Processing silent login request"); + + context.CheckForBffMiddleware(Options); var pathBase = context.Request.PathBase; - var redirectPath = pathBase + _options.SilentLoginCallbackPath; + var redirectPath = pathBase + Options.SilentLoginCallbackPath; var props = new AuthenticationProperties { @@ -41,6 +54,8 @@ public async Task ProcessRequestAsync(HttpContext context) }, }; + Logger.LogDebug("Silent login endpoint triggering Challenge with returnUrl {redirectUri}", redirectPath); + await context.ChallengeAsync(props); } } \ No newline at end of file diff --git a/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs b/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs index 990a572b..105dbc32 100644 --- a/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs +++ b/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs @@ -47,6 +47,8 @@ public DefaultUserService(IOptions options, ILoggerFactory loggerFac /// public virtual async Task ProcessRequestAsync(HttpContext context) { + Logger.LogDebug("Processing user request"); + context.CheckForBffMiddleware(Options); var result = await context.AuthenticateAsync(); @@ -63,6 +65,8 @@ public virtual async Task ProcessRequestAsync(HttpContext context) { context.Response.StatusCode = 401; } + + Logger.LogDebug("User endpoint indicates the user is not logged in, using status code {code}", context.Response.StatusCode); } else { @@ -75,6 +79,8 @@ public virtual async Task ProcessRequestAsync(HttpContext context) context.Response.StatusCode = 200; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(json, Encoding.UTF8); + + Logger.LogTrace("User endpoint indicates the user is logged in with claims {claims}", claims); } } diff --git a/src/Duende.Bff/SessionManagement/Revocation/SessionRevocationService.cs b/src/Duende.Bff/SessionManagement/Revocation/SessionRevocationService.cs index 621d1fd9..369acc85 100644 --- a/src/Duende.Bff/SessionManagement/Revocation/SessionRevocationService.cs +++ b/src/Duende.Bff/SessionManagement/Revocation/SessionRevocationService.cs @@ -48,6 +48,8 @@ public async Task RevokeSessionsAsync(UserSessionsFilter filter, CancellationTok filter.SessionId = null; } + _logger.LogDebug("Revoking sessions for sub {sub} and sid {sid}", filter.SubjectId, filter.SessionId); + if (_options.RevokeRefreshTokenOnLogout) { var tickets = await _ticketStore.GetUserTicketsAsync(filter); @@ -59,15 +61,8 @@ public async Task RevokeSessionsAsync(UserSessionsFilter filter, CancellationTok if (!String.IsNullOrWhiteSpace(refreshToken)) { await _tokenEndpoint.RevokeRefreshTokenAsync(refreshToken, new UserTokenRequestParameters(), cancellationToken); - // todo: no more error response - is logging required? - // if (response.IsError) - // { - // _logger.LogDebug("Error revoking refresh token: {error} for subject id: {sub} and session id: {sid}", response.Error, ticket.GetSubjectId(), ticket.GetSessionId()); - // } - // else - // { - // _logger.LogDebug("Refresh token revoked successfully for subject id: {sub} and session id: {sid}", ticket.GetSubjectId(), ticket.GetSessionId()); - // } + + _logger.LogDebug("Refresh token revoked for sub {sub} and sid {sid}", ticket.GetSubjectId(), ticket.GetSessionId()); } } } diff --git a/src/Duende.Bff/SessionManagement/TicketStore/ServerSideTicketStore.cs b/src/Duende.Bff/SessionManagement/TicketStore/ServerSideTicketStore.cs index d1a741b6..dfd3fadb 100644 --- a/src/Duende.Bff/SessionManagement/TicketStore/ServerSideTicketStore.cs +++ b/src/Duende.Bff/SessionManagement/TicketStore/ServerSideTicketStore.cs @@ -10,6 +10,7 @@ using IdentityModel; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.Extensions.Logging; namespace Duende.Bff; @@ -141,6 +142,8 @@ public Task RemoveAsync(string key) /// public async Task> GetUserTicketsAsync(UserSessionsFilter filter, CancellationToken cancellationToken) { + _logger.LogDebug("Getting AuthenticationTickets from store for sub {sub} sid {sid}", filter.SubjectId, filter.SessionId); + var list = new List(); var sessions = await _store.GetUserSessionsAsync(filter, cancellationToken);