gomicrosoft-teamswebhookshmac

MS Teams outgoing webhook HMAC authentication


I'm trying to set a MS Teams outgoing webhook with golang & Google App Engine (deployed with gcloud app deploy):

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "strings"
)

var secret string = "THIS_IS_A_SECRET"

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Webhook endpoint hit")
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Can't read request body", http.StatusBadRequest)
        return
    }
    fmt.Println("Received webhook request:", string(body))

    // Log the request body for debugging
    fmt.Printf("Request Body: %s\n", string(body))

    // Generate HMAC token from the request body
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expectedMAC := mac.Sum(nil)
    expectedMACBase64 := base64.StdEncoding.EncodeToString(expectedMAC)

    // Get the HMAC token from the request header
    authHeader := r.Header.Get("Authorization")
    if !strings.HasPrefix(authHeader, "HMAC ") {
        fmt.Println("Invalid Authorization header")
        http.Error(w, "Invalid Authorization header", http.StatusUnauthorized)
        return
    }
    providedMACBase64 := strings.TrimPrefix(authHeader, "HMAC ")

    // Compare the generated HMAC token with the provided one
    if !hmac.Equal([]byte(providedMACBase64), []byte(expectedMACBase64)) {
        fmt.Println("Invalid HMAC token")
        fmt.Println("Expected HMAC token:", expectedMACBase64)
        fmt.Println("Provided HMAC token:", providedMACBase64)
        http.Error(w, "Invalid HMAC token", http.StatusUnauthorized)
        return
    } else {
        fmt.Println("Authenticated: Valid HMAC token")
    }

    // Create a response in the format expected by Microsoft Teams
    response := map[string]string{
        "type": "message",
        "text": "Webhook received successfully",
    }

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)

    json.NewEncoder(w).Encode(response)
}

func main() {
    http.HandleFunc("/teams-webhook", handleWebhook)
    http.ListenAndServe(":8080", nil)
}

However, the expected HMAC and provided HMAC are always different. I think there must be something wrong with my HMAC algorithm. Anyone knows what's wrong here?

For reference: I'm following the guide on https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-outgoing-webhook?tabs=verifyhmactoken%2Cdotnet


Solution

  • OK... it turns out that I forgot to decode the Base64 encoded key:

    []byte(secret) > base64.StdEncoding.DecodeString(secret)