在 dotnetcore 中添加到 Slack,而没有 Identity Framework 错误:oauth 状态丢失或无效

ajt*_*tum 6 c# slack-api .net-core slack

我正在尝试为我的 slackbot 创建一个非常简单的页面,以便用户可以登录和注册。但是,即使使用他们生成的“使用 Slack 登录”按钮时,我也会收到错误“oauth 状态丢失或无效。”。“添加到 Slack”也会发生同样的错误。

我的代码基于https://dotnetthoughts.net/slack-authentication-with-aspnet-core/。尽管它已经过时了,但它是我在网上能找到的唯一示例。我试图弄清楚我需要改变什么才能让它与 dotnetcore 3 和 Slack 2.0 一起工作,但我已经无能为力了。

在我的服务中,在调用 AddMvc 等之前,我有以下内容。

services.AddAuthentication(options =>
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.Cookie.Name = "MyAuthCookieName";
        options.Cookie.HttpOnly = true;
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        options.Cookie.MaxAge = TimeSpan.FromDays(7);
        options.ExpireTimeSpan = TimeSpan.FromDays(7);

        options.LoginPath = $"/login";
        options.LogoutPath = $"/logout";
        options.AccessDeniedPath = $"/AccessDenied";
        options.SlidingExpiration = true;
        options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
    })
    //.AddSlack(options =>
    //{
    //    options.ClientId = Configuration["Slack:ClientId"];
    //    options.ClientSecret = Configuration["Slack:ClientSecret"];
    //});
    .AddOAuth("Slack", options =>
    {
        options.ClientId = Configuration["Slack:ClientId"];
        options.ClientSecret = Configuration["Slack:ClientSecret"];
        options.CallbackPath = new PathString("/signin-slack");
        options.AuthorizationEndpoint = $"https://slack.com/oauth/authorize";
        options.TokenEndpoint = "https://slack.com/api/oauth.access";
        options.UserInformationEndpoint = "https://slack.com/api/users.identity?token=";
        options.Scope.Add("identity.basic");
        options.Events = new OAuthEvents()
        {
            OnCreatingTicket = async context =>
            {
                var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint + context.AccessToken);
                var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
                response.EnsureSuccessStatusCode();
                var userObject = JObject.Parse(await response.Content.ReadAsStringAsync());
                var user = userObject.SelectToken("user");
                var userId = user.Value<string>("id");

                if (!string.IsNullOrEmpty(userId))
                {
                    context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId, ClaimValueTypes.String, context.Options.ClaimsIssuer));
                }

                var fullName = user.Value<string>("name");
                if (!string.IsNullOrEmpty(fullName))
                {
                    context.Identity.AddClaim(new Claim(ClaimTypes.Name, fullName, ClaimValueTypes.String, context.Options.ClaimsIssuer));
                }
            }
        };
    });
Run Code Online (Sandbox Code Playgroud)

我的配置方法看起来像

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.Map("/login", builder =>
{
    builder.Run(async context =>
    {
        await context.ChallengeAsync("Slack", properties: new AuthenticationProperties { RedirectUri = "/" });
    });
});

app.Map("/logout", builder =>
{
    builder.Run(async context =>
    {
        await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        context.Response.Redirect("/");
    });
});

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapRazorPages();
});
Run Code Online (Sandbox Code Playgroud)

除了“无效的 oauth 状态丢失”之外,如果在我的应用程序中我直接转到 /login,我不会收到错误消息,但我似乎没有以User.Identity.IsAuthenticatedfalse登录。

我真的很茫然,可以使用一些非常感谢的帮助!

谢谢!

大规模更新

我让登录进入 slack 工作,但我无法让添加到 Slack 按钮工作。

这是我的新服务:

services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    })
    .AddCookie(options =>
    {
        options.LoginPath = "/login";
        options.LogoutPath = "/logout";
    })
     .AddSlack(options =>
    {
        options.ClientId = Configuration["Slack:ClientId"];
        options.ClientSecret = Configuration["Slack:ClientSecret"];
        options.CallbackPath =  $"{SlackAuthenticationDefaults.CallbackPath}?state={Guid.NewGuid():N}";
        options.ReturnUrlParameter = new PathString("/");
        options.Events = new OAuthEvents()
        {
            OnCreatingTicket = async context =>
            {
                var request = new HttpRequestMessage(HttpMethod.Get, $"{context.Options.UserInformationEndpoint}?token={context.AccessToken}");
                var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
                response.EnsureSuccessStatusCode();
                var userObject = JObject.Parse(await response.Content.ReadAsStringAsync());
                var user = userObject.SelectToken("user");
                var userId = user.Value<string>("id");

                if (!string.IsNullOrEmpty(userId))
                {
                    context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId, ClaimValueTypes.String, context.Options.ClaimsIssuer));
                }

                var fullName = user.Value<string>("name");
                if (!string.IsNullOrEmpty(fullName))
                {
                    context.Identity.AddClaim(new Claim(ClaimTypes.Name, fullName, ClaimValueTypes.String, context.Options.ClaimsIssuer));
                }
            }
        };
    });
Run Code Online (Sandbox Code Playgroud)

根据@timur,我抓取了我的 app.Map 并使用了身份验证控制器:

public class AuthenticationController : Controller
{
    [HttpGet("~/login")]
    public async Task<IActionResult> SignIn()
    {
        return Challenge(new AuthenticationProperties { RedirectUri = "/" }, "Slack");
    }

    [HttpGet("~/signin-slack")]
    public IActionResult SignInSlack()
    {
        return RedirectToPage("/Index");
    }

    [HttpGet("~/logout"), HttpPost("~/logout")]
    public IActionResult SignOut()
    {
        return SignOut(new AuthenticationProperties { RedirectUri = "/" },
            CookieAuthenticationDefaults.AuthenticationScheme);
    }
}
Run Code Online (Sandbox Code Playgroud)

Slack 提供了“添加到 Slack”按钮。

<a href="https://slack.com/oauth/authorize?scope=incoming-webhook,commands,bot&client_id=#############"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>
Run Code Online (Sandbox Code Playgroud)

所以,当用户点击“登录”时,它会登录他们并得到他们的名字等。你会注意到在我的身份验证控制器中我添加了一个路径为“~/signin-slack”的函数,这是因为我手动添加了“Options.CallbackPath”添加状态参数。如果我删除“Options.CallbackPath”,我会收到一条错误消息,指出 oauth 状态丢失或无效。

所以,我不确定我在 Slack 方面缺少什么。他们让这听起来很容易!

很抱歉长时间发布/更新。谢谢你的帮助。

ajt*_*tum 3

所以我想通了。登录与“添加到 Slack”功能完全分开。

因此,为了登录,我的服务如下:

var slackState = Guid.NewGuid().ToString("N");

services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        })
        .AddCookie(options =>
        {
            options.LoginPath = "/login";
            options.LogoutPath = "/logout";
        })
         .AddSlack(options =>
        {
            options.ClientId = Configuration["Slack:ClientId"];
            options.ClientSecret = Configuration["Slack:ClientSecret"];
            options.CallbackPath = $"{SlackAuthenticationDefaults.CallbackPath}?state={slackState}";
            options.ReturnUrlParameter = new PathString("/");
            options.Events = new OAuthEvents()
            {
                OnCreatingTicket = async context =>
                {
                    var request = new HttpRequestMessage(HttpMethod.Get, $"{context.Options.UserInformationEndpoint}?token={context.AccessToken}");
                    var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
                    response.EnsureSuccessStatusCode();
                    var userObject = JObject.Parse(await response.Content.ReadAsStringAsync());
                    var user = userObject.SelectToken("user");
                    var userId = user.Value<string>("id");


                    if (!string.IsNullOrEmpty(userId))
                    {
                        context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId, ClaimValueTypes.String, context.Options.ClaimsIssuer));
                    }

                    var fullName = user.Value<string>("name");
                    if (!string.IsNullOrEmpty(fullName))
                    {
                        context.Identity.AddClaim(new Claim(ClaimTypes.Name, fullName, ClaimValueTypes.String, context.Options.ClaimsIssuer));
                    }
                }
            };
        });
Run Code Online (Sandbox Code Playgroud)

我的 AuthenticationController 现在看起来像:

public class AuthenticationController : Controller
{
    private readonly ILogger<AuthenticationController> _logger;
    private readonly AppSettings _appSettings;

    public AuthenticationController(ILogger<AuthenticationController> logger, IOptionsMonitor<AppSettings> appSettings)
    {
        _logger = logger;
        _appSettings = appSettings.CurrentValue;
    }

    [HttpGet("~/login")]
    public IActionResult SignIn()
    {
        return Challenge(new AuthenticationProperties { RedirectUri = "/" }, "Slack");
    }

    [HttpGet("~/signin-slack")]
    public async Task<IActionResult> SignInSlack()
    {
        var clientId = _appSettings.Slack.ClientId;
        var clientSecret = _appSettings.Slack.ClientSecret;
        var code = Request.Query["code"];

        SlackAuthRequest slackAuthRequest;
        string responseMessage;

        var requestUrl = $"https://slack.com/api/oauth.access?client_id={clientId}&client_secret={clientSecret}&code={code}";
        var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
        using (var client = new HttpClient())
        {
            var response = await client.SendAsync(request).ConfigureAwait(false);
            var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            slackAuthRequest = JsonConvert.DeserializeObject<SlackAuthRequest>(result);
        }

        if (slackAuthRequest != null)
        {
            _logger.LogInformation("New installation of StanLeeBot for {TeamName} in {Channel}", slackAuthRequest.TeamName, slackAuthRequest.IncomingWebhook.Channel);

            var webhookUrl = slackAuthRequest.IncomingWebhook.Url;

            var sbmClient = new SbmClient(webhookUrl);
            var message = new Message
            {
                Text = "Hi there from StanLeeBot!"
            };
            await sbmClient.SendAsync(message).ConfigureAwait(false);

            responseMessage = $"Congrats! StanLeeBot has been successfully added to {slackAuthRequest.TeamName} {slackAuthRequest.IncomingWebhook.Channel}";
            return RedirectToPage("/Index", new { message = responseMessage });
        }

        _logger.LogError("Something went wrong making a request to {RequestUrl}", requestUrl);

        responseMessage = "Error: Something went wrong and we were unable to add StanLeeBot to your Slack.";
        return RedirectToPage("/Index", new { message = responseMessage });
    }

    [HttpGet("~/logout"), HttpPost("~/logout")]
    public IActionResult SignOut()
    {
        return SignOut(new AuthenticationProperties { RedirectUri = "/" },
            CookieAuthenticationDefaults.AuthenticationScheme);
    }
}
Run Code Online (Sandbox Code Playgroud)

SmbClient 是一个名为 SlackBotMessages 的 Nuget 包,用于发送消息。因此,在用户进行身份验证后,系统会自动向该频道发送欢迎用户的消息。

非常感谢大家的帮助!让我知道您的想法或者您是否发现任何问题。