How to authorize and post Slack messages from ASP.NET Core 💬
All of your favorite tools (elmah.io anyone?) integrate with online messaging tools like Slack. It's essential for moderne companies to be notified on Slack when new errors are logged, when a build fails, when customers are acquired, etc. In this post, I'll show you how to create an ASP.NET Core web app that lets the user authorize their Slack workspace, select a channel, and post messages to that channel from C#.
You probably already tried connecting online apps with Slack. You start by clicking an Authorize button, the application redirects you to Slack where you sign in and select a channel. Slack then redirects you back to the application and the integration is set up. The application now has access to post messages to the selected channel. Here's an example of how to set that up in an ASP.NET Core website.
Start by navigating to https://api.slack.com/apps and click the Create New App button. In the modal, select the From scratch option. Give the app a proper name and select a workspace. Finally, click the Create App button:
This will create a new Slack app. Keep the Basic Information page open, since that contains the app ID and secret which we will need in a moment.
Create a new ASP.NET Core website. For this example, I have chosen an MVC project, but it could be another project type as well. Include a big Authorize button on the front page:
<a href="/home/authorizeslack" class="btn btn-primary btn-lg">Authorize Slack</a>
Create the AuthorizeSlack
action in the HomeController
class:
public IActionResult AuthorizeSlack()
{
return Redirect(
"https://slack.com/oauth/v2/authorize?client_id=YOUR_CLIENT_IDscope=incoming-webhook");
}
The endpoint redirects the user to Slack's authorize page. You will need to replace YOUR_CLIENT_ID
with the client ID found on the Basic Information page on your slack app. By including a scope
of incoming-webhook
, we ask Slack for permission to post messages to a Slack channel using a webhook URL.
Slack needs to know where to redirect the user once the authorization is given by the user. Navigate back to the Slack app and select the OAuth & Permission menu item. Beneath the Redirect URLs section, include the following URL:
localhost:44329
is the hostname and port number assigned to my local application. You'd want to replace those with values from your code. To make this run on both local machines and your various environments (staging, production, etc.) you can include additional redirect URLs in the list.
Go back to your code and implement the SlackCallback
action in the HomeController
class:
public async Task<IActionResult> SlackCallback(string state, string code = null, string error = null)
{
// TODO
}
This is a GET endpoint that will be requested by Slack. To avoid someone requesting this page in the browser, we need to verify that the request came from Slack. To do this, set up a new HttpClient
in the ConfigureServices
method in the Startup
class:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// ...
}
Then inject a HttpClient
in the HomeController
constructor:
private readonly HttpClient httpClient;
public HomeController(HttpClient httpClient)
{
this.httpClient = httpClient;
}
Include the following code in the SlackCallback
method:
var response = await httpClient.GetFromJsonAsync<SlackResponse>(
$"https://slack.com/api/oauth.v2.access?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&code={code}");
var ok = response.ok;
if (!ok) return Redirect("/");
var webhookUrl = response.incoming_webhook?.url;
if (string.IsNullOrWhiteSpace(webhookUrl)) return Redirect("/");
// TODO: Store webhookURL somewhere
The code uses the injected HttpClient
to request the api/oauth.v2.access
endpoint. The request includes your client IP, client secret, and the code provided for the SlackCallback
action when requested by Slack. The client secret is found on your Slack app as well.
I'm using the System.Net.Http.Json
helpers to map the JSON response directly to a small strongly typed object made exclusively for this one purpose:
private class SlackResponse
{
public bool ok { get; set; }
public Webhook incoming_webhook { get; set; }
}
private class Webhook
{
public string url { get; set; }
}
The ok
boolean indicates if Slack accepts the call or not. The incoming_webhook.url
value includes an URL generated from Slack. You will need to store this URL somewhere to post messages to the chosen Slack channel.
To test the generated webhook URL, include the following code before returning from the SlackCallback
method:
await httpClient.PostAsJsonAsync(webhookUrl, new { text = "Hello from app" });
This will use the generated webhook URL to post a message to Slack saying hello.
Here's the full code for the HomeController
class:
public class HomeController : Controller
{
private readonly HttpClient httpClient;
public HomeController(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public IActionResult Index()
{
return View();
}
public IActionResult AuthorizeSlack()
{
return Redirect(
"https://slack.com/oauth/v2/authorize?client_id=YOUR_CLIENT_ID&scope=incoming-webhook");
}
private class SlackResponse
{
public bool ok { get; set; }
public Webhook incoming_webhook { get; set; }
}
private class Webhook
{
public string url { get; set; }
}
public async Task<IActionResult> SlackCallback(string state, string code = null, string error = null)
{
var response = await httpClient.GetFromJsonAsync<SlackResponse>(
$"https://slack.com/api/oauth.v2.access?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&code={code}");
var ok = response.ok;
if (!ok) return Redirect("/");
var webhookUrl = response.incoming_webhook?.url;
if (string.IsNullOrWhiteSpace(webhookUrl)) return Redirect("/");
// Store webhook URL somewhere
await httpClient.PostAsJsonAsync(webhookUrl, new { text = "Hello from app" });
return Redirect("/");
}
}