/// <summary>
    /// Create the claims to be used in the back-channel logout token.
    /// </summary>
    /// <param name="request"></param>
    /// <returns>The claims to include in the token.</returns>
    protected Task <IEnumerable <Claim> > CreateClaimsForTokenAsync(BackChannelLogoutRequest request)
    {
        if (request.SessionIdRequired && request.SessionId == null)
        {
            throw new ArgumentException("Client requires SessionId", nameof(request.SessionId));
        }
        if (request.SubjectId == null && request.SessionId == null)
        {
            throw new ArgumentException("Either a SubjectId or SessionId is required.");
        }

        var json = "{\"" + OidcConstants.Events.BackChannelLogout + "\":{} }";

        var claims = new List <Claim>
        {
            new Claim(JwtClaimTypes.Audience, request.ClientId),
            new Claim(JwtClaimTypes.JwtId, CryptoRandom.CreateUniqueId(16, CryptoRandom.OutputFormat.Hex)),
            new Claim(JwtClaimTypes.Events, json, IdentityServerConstants.ClaimValueTypes.Json)
        };

        if (request.SubjectId != null)
        {
            claims.Add(new Claim(JwtClaimTypes.Subject, request.SubjectId));
        }

        if (request.SessionId != null)
        {
            claims.Add(new Claim(JwtClaimTypes.SessionId, request.SessionId));
        }

        return(Task.FromResult(claims.AsEnumerable()));
    }
    /// <summary>
    /// Creates the form-url-encoded payload (as a dictionary) to send to the client.
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    protected async Task <Dictionary <string, string> > CreateFormPostPayloadAsync(BackChannelLogoutRequest request)
    {
        var token = await CreateTokenAsync(request);

        var data = new Dictionary <string, string>
        {
            { OidcConstants.BackChannelLogoutRequest.LogoutToken, token }
        };

        return(data);
    }
    /// <summary>
    /// Creates the JWT used for the back-channel logout notification.
    /// </summary>
    /// <param name="request"></param>
    /// <returns>The token.</returns>
    protected virtual async Task <string> CreateTokenAsync(BackChannelLogoutRequest request)
    {
        var claims = await CreateClaimsForTokenAsync(request);

        if (claims.Any(x => x.Type == JwtClaimTypes.Nonce))
        {
            throw new InvalidOperationException("nonce claim is not allowed in the back-channel signout token.");
        }

        if (request.Issuer != null)
        {
            return(await Tools.IssueJwtAsync(DefaultLogoutTokenLifetime, request.Issuer, claims));
        }

        return(await Tools.IssueJwtAsync(DefaultLogoutTokenLifetime, claims));
    }
    /// <inheritdoc/>
    public async Task <IEnumerable <BackChannelLogoutRequest> > GetBackChannelLogoutNotificationsAsync(LogoutNotificationContext context)
    {
        using var activity = Tracing.ServiceActivitySource.StartActivity("LogoutNotificationService.GetBackChannelLogoutNotifications");

        var backChannelLogouts = new List <BackChannelLogoutRequest>();

        foreach (var clientId in context.ClientIds)
        {
            var client = await _clientStore.FindEnabledClientByIdAsync(clientId);

            if (client != null)
            {
                if (client.BackChannelLogoutUri.IsPresent())
                {
                    var back = new BackChannelLogoutRequest
                    {
                        ClientId          = clientId,
                        LogoutUri         = client.BackChannelLogoutUri,
                        SubjectId         = context.SubjectId,
                        SessionId         = context.SessionId,
                        SessionIdRequired = client.BackChannelLogoutSessionRequired,
                        Issuer            = context.Issuer,
                    };

                    backChannelLogouts.Add(back);
                }
            }
        }

        if (backChannelLogouts.Any())
        {
            var msg = backChannelLogouts.Select(x => x.LogoutUri).Aggregate((x, y) => x + ", " + y);
            _logger.LogDebug("Client back-channel logout URLs: {0}", msg);
        }
        else
        {
            _logger.LogDebug("No client back-channel logout URLs");
        }

        return(backChannelLogouts);
    }
 /// <summary>
 /// Performs the HTTP POST of the logout payload to the client.
 /// </summary>
 /// <param name="client"></param>
 /// <param name="data"></param>
 /// <returns></returns>
 protected virtual Task PostLogoutJwt(BackChannelLogoutRequest client, Dictionary <string, string> data)
 {
     return(HttpClient.PostAsync(client.LogoutUri, data));
 }
    /// <summary>
    /// Performs the back-channel logout for a single client.
    /// </summary>
    /// <param name="request"></param>
    protected virtual async Task SendLogoutNotificationAsync(BackChannelLogoutRequest request)
    {
        var data = await CreateFormPostPayloadAsync(request);

        await PostLogoutJwt(request, data);
    }