I'm trying to create a very simple page for my slackbot so that users can login and register. However, even when using their generated "Login with Slack" button I receive an error "The oauth state was missing or invalid.". The same error happens with "Add to Slack".
I based my code off of https://dotnetthoughts.net/slack-authentication-with-aspnet-core/. Even though it's outdated, it's the only example I could find online. I tried figuring out what I need to change in order to get it to work with the dotnetcore 3 and Slack 2.0, but I've come to my wits end.
In my services, I have the following before calling AddMvc, etc.
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));
}
}
};
});
My configure method looks like
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();
});
Besides the "oauth state was missing on invalid", if in my app I directly go to /login I don't receive the error, but it doesn't appear that I'm logged in as User.Identity.IsAuthenticated
is false.
I'm really at a loss, and could use some much appreciated help!
Thank you!
MASSIVE UPDATE
I got the log into slack to work, but I cannot get the Add to Slack button to work.
Here is my new services:
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));
}
}
};
});
Per @timur,I scraped my app.Map and went with an Authentication Controller:
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);
}
}
The "Add to Slack" button is provided as is from 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>
So, when the use clicks "Login" it logs them in and I get their name, etc. You'll notice in my Authentication Controller I added a function with the path "~/signin-slack" this is because I manually added the "Options.CallbackPath" to add a state parameter. If I remove "Options.CallbackPath", I get an error stating that the oauth state was missing or invalid.
So, I'm not sure what I'm missing here on the Slack side. They make it sound so easy!
Sorry for the long post/update. Thanks for your help.