Search code examples
azureauthenticationazure-active-directorybotframeworkazure-web-app-service

Authentication for Proactive Message Endpoint


I am building a Bot Framework bot which needs to be triggered proactively from an Azure Function. I have deployed a prototype to Azure based on this example which currently allows me to POST to an /api/notify REST endpoint in order to send messages proactively.

However, I am not sure how to go about adding authentication to the /api/notify endpoint. Messages to the /api/messages endpoint are authenticated using the Bot Service API in the application code, but in that example there is no authentication on traffic to the /api/notify endpoint (I can POST to it using curl from my CLI without any tokens or passphrases).

I tried enabling App Service Authentication on the underlying App Service but then my bot no longer worked in the web chat.

How do I add authentication to this endpoint so only my Azure Function can POST to it?


Solution

  • I actually just set this up for an internal project a couple of weeks ago. You'll probably need to adapt this strategy to whatever language your bot and function are in, but here's what I did:

    Azure Function

    module.exports = class BotService {
        constructor(context) {
            this.context = context;
            // Get appId and password from environment variables to build credentials
            this.credentials = new MicrosoftAppCredentials(process.env.MicrosoftAppId, process.env.MicrosoftAppPassword);
            this.client = axios.create({
                baseURL: process.env.BotBaseUrl
            });
        }
    
        async sendData(body) {
            // Get the auth token using the credentials
            const token = await this.credentials.getToken();
            const response = await this.client.post('/api/data', body, {
                // Add the token to the auth header
                headers: { Authorization: `Bearer ${ token }` }
            });
            if (response.status !== 200) {
                this.context.error(JSON.stringify(response, null, 2));
            } else {
                this.context.log(`Successfully sent data to the bot. Response Code: ${ response.status }`);
            }
        }
    }
    

    Bot Note: The bot is in C# and this is in the controller for /api/data

    [HttpPost]
    public async Task<HttpStatusCode> PostAsync()
    {
        try
        {
            // Build the bot credentials
            var credentials = new SimpleCredentialProvider(Configuration["MicrosoftAppId"], Configuration["MicrosoftAppPassword"]);
            // Grab the auth header from the request
            Request.Headers.TryGetValue("Authorization", out StringValues authHeader);
            // Use Microsoft.Bot.Connector.Authentication.JwtTokenValidation to validate the auth header
            var result = await JwtTokenValidation.ValidateAuthHeader(authHeader, credentials, new SimpleChannelProvider(), Channels.Directline);
    
            if (result.IsAuthenticated)
            {
                // Do stuff
                // Do stuff
                return HttpStatusCode.OK;
            }
    
            return HttpStatusCode.Forbidden;
        }
        catch (Exception e)
        {
            Logger.LogError($"Something went wrong in /api/data controller: {e.Message}");
        }
        return HttpStatusCode.BadRequest;
    }
    

    It looks like your bot is in Python. You can see similar auth validation in one of our Python tests