自定义身份验证 "The oauth state was missing or invalid"

Custom auth "The oauth state was missing or invalid"

我和我的团队创建了一个用于外部 SSO 的自定义 OAuth。它在 localhost 上工作,但一旦我们将它带到我们的暂存环境中,我们就会收到“oauth 状态丢失或无效”。错误。

我们使用“https://auth0.com/”进行测试。

为了尝试解决这个问题,我们覆盖了以下内置方法,通过断点可以看到查询状态返回 null。

protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri);
protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync();

我需要一些帮助来弄清楚为什么这是一个暂存问题而不是本地问题,因为我们有点难过。我们的理论是,这些方法中使用的解码器在 var properties = Options.StateDataFormat.Unprotect(state); 上发生变化,因此因为它们不相同,所以它们无法解码彼此的状态。我将把我们的实现放在下面,如果需要的话我也可以粘贴内置方法,但我无法理解内置函数的问题。

启动:

foreach (var customAuthItem in customAuthList)
                {
                    services.AddAuthentication().AddCustom(customAuthItem.CampaignId, options =>
                    {
                        options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                        options.AuthorizationEndpoint = customAuthItem.AuthEndpoint;
                        options.TokenEndpoint = customAuthItem.TokenEndpoint;
                        options.UserInformationEndpoint = customAuthItem.UserInfoEndpoint;
                        options.ClientId = customAuthItem.ClientId;
                        options.ClientSecret = customAuthItem.ClientSecret;
                    });
                }

选项:

public class CustomAuthenticationOptions : OAuthOptions
{
    /// <summary>
    /// Initializes a new instance of the <see cref="CustomAuthenticationOptions"/> class.
    /// </summary>
    public CustomAuthenticationOptions()
    {
        ClaimsIssuer = CustomAuthenticationDefaults.Issuer;
        CallbackPath = CustomAuthenticationDefaults.CallbackPath;

        AuthorizationEndpoint = CustomAuthenticationDefaults.AuthorizationEndpoint;
        TokenEndpoint = CustomAuthenticationDefaults.TokenEndpoint;
        UserInformationEndpoint = CustomAuthenticationDefaults.UserInformationEndpoint;

        Scope.Add("openid");
        Scope.Add("profile");
        Scope.Add("email");

        ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
        ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
        ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
    }

    /// Gets the list of fields to retrieve from the user information endpoint.
    /// </summary>
    public ISet<string> Fields { get; } = new HashSet<string>
    {
        "email",
        "name",
        "sub"
    };

默认值:

public static class CustomAuthenticationDefaults
{
    /// <summary>
    /// Default value for <see cref="AuthenticationScheme.Name"/>.
    /// </summary>
    public const string AuthenticationScheme = "Custom";

    /// <summary>
    /// Default value for <see cref="AuthenticationScheme.DisplayName"/>.
    /// </summary>
    public static readonly string DisplayName = "Custom";

    /// <summary>
    /// Default value for <see cref="AuthenticationSchemeOptions.ClaimsIssuer"/>.
    /// </summary>
    public static readonly string Issuer = "Custom";

    /// <summary>
    /// Default value for <see cref="RemoteAuthenticationOptions.CallbackPath"/>.
    /// </summary>
    public static readonly string CallbackPath = "/signin-custom";

    /// <summary>
    /// Default value for <see cref="OAuthOptions.AuthorizationEndpoint"/>.
    /// </summary>
    public static readonly string AuthorizationEndpoint = "https://dev-egd511ku.us.auth0.com/authorize";

    /// <summary>
    /// Default value for <see cref="OAuthOptions.TokenEndpoint"/>.
    /// </summary>
    public static readonly string TokenEndpoint = "https://dev-egd511ku.us.auth0.com/oauth/token";

    /// <summary>
    /// Default value for <see cref="OAuthOptions.UserInformationEndpoint"/>.
    /// </summary>
    public static readonly string UserInformationEndpoint = "https://dev-egd511ku.us.auth0.com/userinfo";
}

处理程序:

protected override async Task<AuthenticationTicket> CreateTicketAsync(
        [NotNull] ClaimsIdentity identity,
        [NotNull] AuthenticationProperties properties,
        [NotNull] OAuthTokenResponse tokens)
    {
        Serilog.Log.Debug("CustomAuthenticationHandler.CreateTicketAsync: STARTED!");

        string endpoint = Options.UserInformationEndpoint;

        if (Options.Fields.Count > 0)
        {
            endpoint = QueryHelpers.AddQueryString(endpoint, "fields", string.Join(',', Options.Fields));
        }

        using var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);

        Serilog.Log.Debug("CustomAuthenticationHandler.CreateTicketAsync: ABOUT TO SEND REQUEST!");

        using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);
        if (!response.IsSuccessStatusCode)
        {
            Serilog.Log.Debug($"CustomAuthenticationHandler.CreateTicketAsync: FAILED REQUEST: {response.ReasonPhrase}");

            await Log.UserProfileErrorAsync(Logger, response, Context.RequestAborted);
            throw new HttpRequestException("An error occurred while retrieving the user profile from Custom.");
        }

        var payloadString = await response.Content.ReadAsStringAsync();

        Serilog.Log.Debug($"CustomAuthenticationHandler.CreateTicketAsync: PAYLOAD: {payloadString}");

        using var payload = JsonDocument.Parse(payloadString);// Context.RequestAborted));

        var principal = new ClaimsPrincipal(identity);
        var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);
        context.RunClaimActions();

        await Events.CreatingTicket(context);
        return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name);
    }

编辑:成功登录并重定向回我们的站点后收到错误。我可以通过哨兵面包屑看到状态是正确的,所以这似乎是一个解密问题。

原来问题是因为我AddAuthentication()两次,它忽略了auth方法的follow-up注册,导致只有一个OAuth工作。这有点问题,因为我们希望为我们的客户支持多个 SSO 选项,但可能需要找出不同的方法。我很高兴我终于知道问题出在哪里了。