Last active
October 22, 2021 15:10
-
-
Save eman41/683e7a9cf727921c1dc9d5fd5af54c4f to your computer and use it in GitHub Desktop.
Example verifying a Slack slash command POST request (C#, ASP.NET, Core 3.1)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// In ASP .NET Core 3.1, there is some required middleware in Startup.cs to enable reading the raw body text more than once | |
// Startup.cs | |
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | |
{ | |
if (env.IsDevelopment()) | |
{ | |
app.UseDeveloperExceptionPage(); | |
} | |
else | |
{ | |
app.UseExceptionHandler("/Error"); | |
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. | |
app.UseHsts(); | |
} | |
// !!!!! REQUIRED FOR READING THE FULL BODY TEXT IN AN API CONTROLLER | |
// This must come before mapping routes/controllers | |
app.Use(next => context => | |
{ | |
context.Request.EnableBuffering(); | |
return next(context); | |
}); | |
// !!!!! END MIDDLEWARE CODE. Below this line is standard initialization | |
app.UseHttpsRedirection(); | |
app.UseStaticFiles(); | |
app.UseRouting(); | |
app.UseAuthorization(); | |
app.UseEndpoints(endpoints => | |
{ | |
endpoints.MapRazorPages(); | |
endpoints.MapControllers(); | |
}); | |
} | |
///////////////////////////////////////////////////////////////////////////////////////////////////////// | |
using System; | |
using System.Security.Cryptography; | |
using System.Text; | |
using Microsoft.AspNetCore.Http; | |
public enum VerificationStatus | |
{ | |
/// <summary> | |
/// Request is valid and may be processed | |
/// </summary> | |
Ok = 0, | |
/// <summary> | |
/// Request is too old and rejected | |
/// </summary> | |
Expired, | |
/// <summary> | |
/// Request could not be validated | |
/// </summary> | |
Invalid, | |
/// <summary> | |
/// Verification service is not ready or has not been properly setup | |
/// </summary> | |
NotReady | |
} | |
public interface IVerificationService | |
{ | |
/// <summary> | |
/// Verify the provided web request | |
/// </summary> | |
/// <param name="toVerify">Incoming http request</param> | |
/// <returns>Verification result</returns> | |
VerificationStatus Verify(HttpContext toVerify); | |
} | |
// Uses an IVerificationService interface for better integration with ASP's Service provider | |
public class SlackVerificationService : IVerificationService | |
{ | |
private string _secret; | |
private const string SlackEnvVar = "SLACK_SIGNING_SECRET"; | |
public void Init(bool allowPassThrough = true) | |
{ | |
// There are lots of methods for storing/retrieving the signing secret | |
// This one is good for development, but you probably want to use a platform | |
// supported vault or something like that. In most cases EnvVars are fine. | |
_secret = Environment.GetEnvironmentVariable(SlackEnvVar); | |
} | |
public VerificationStatus Verify(HttpContext toVerify) | |
{ | |
if (string.IsNullOrEmpty(_secret)) | |
{ | |
return VerificationStatus.NotReady; | |
} | |
var buffer = new byte[Convert.ToInt32(toVerify.Request.ContentLength)]; | |
toVerify.Request.Body.ReadAsync(buffer, 0, buffer.Length); | |
string body = Encoding.UTF8.GetString(buffer); | |
string timestamp = toVerify.Request.Headers["X-Slack-Request-Timestamp"]; | |
// We don't test message age in this example | |
string signature = toVerify.Request.Headers["X-Slack-Signature"]; | |
if (timestamp != null && signature != null) | |
{ | |
Encoding encoding = Encoding.UTF8; | |
string verificationString = $"v0:{timestamp}:{body}"; | |
var hmac = new HMACSHA256(encoding.GetBytes(_secret)); | |
byte[] hashBytes = hmac.ComputeHash(encoding.GetBytes(verificationString)); | |
// This line cleans up the hex version of the hash to match Slack's signature format | |
string hexDigest = "v0=" + BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); | |
int compare = string.Compare(hexDigest, signature, StringComparison.CurrentCulture); | |
if (compare == 0) | |
{ | |
return VerificationStatus.Ok; | |
} | |
} | |
return VerificationStatus.Invalid; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment