Enhanced Direct Line Authentication Features

This blog covers the new enhanced authentication features available for the Direct Line channel. For implementation examples of the concepts discussed here, please see this blog post.

Azure Bot Service authentication

The features discussed here are built on top of Azure Bot Service authentication. Azure Bot Service authentication enables you to authenticate users to and get access tokens from a variety of identity providers such as Azure Active Directory, GitHub, Uber and so on. You can even configure authentication for a custom OAuth2 identity provider. These capabilities enable you to write one piece of authentication code that works across all supported identity providers and channels.

To utilize these capabilities you need to

1. Statically configure “settings” on your bot that contains the details of your application registration with an identity provider.

2. Use an OAuthCard (backed by the application information you supplied in 1) to sign-in a user.

3. Retrieve access tokens through Azure Bot Service API.

Security considerations

When you use Azure Bot Service authentication with Web Chat there are a couple of important security considerations.

The first is the need to prevent impersonation. Impersonation here means an attacker makes the bot thinks he’s someone else. In Web Chat, an attacker can impersonate someone else by changing the user ID of his Web Chat instance. To prevent this we recommend bot developers make the user ID unguessable. If you enable enhanced authentication options, Azure Bot Service can further detect and reject any user ID change. This means the user ID (Activity.From.Id) on messages from Direct Line to your bot will always be the same as the one you initialized Web Chat with. Note that this feature requires the user ID starts with “dl_”.

The second has to do with the fact that there are two user identities involved: one is the user’s identity in a channel and the other is the user’s identity in an identity provider that the bot is interested in. When a bot asks user A in a channel to sign-in to an identity provider P, the sign-in process needs to ensure that user A is the one that signs into P. If another user B is allowed to sign-in, then user A would have access to user B’s resource through the bot. In Web Chat we have 2 mechanisms for ensuring the right user signed in.

1. At the end of sign-in, we presented the user with a randomly generated 6-digit code (aka magic code). The user must type this code in the conversation that initiated the sign-in to complete the sign-in process. This mechanism tends to result in a bad user experience. Additionally, it is still susceptible to phishing attacks. A malicious user can trick another user to sign-in and obtain the magic code through phishing.

2. Because of the issues with 1 above we implemented protection in Azure Bot Service that removed the need for the magic code. Azure Bot Service guarantees that the sign-in process can only be completed in the same browser session as Web Chat itself. To enable this protection, the bot developer needs to start Web Chat with a Direct Line token that contains a list of trusted domains that can host the bot’s Web Chat client. Before, you can only obtain this token by passing an undocumented optional parameter to the Direct Line token API. Now with enhanced authentication options, you can statically specify the trusted domain (origin) list in the Direct Line configuration page.

Code examples

Here is an example ASP.NET controller that works with enhanced authentication options enabled and returns a Direct Line Token and user ID:

public class HomeController : Controller
{
    public async Task<ActionResult> Index()
    {
        var secret = GetSecret();

        HttpClient client = new HttpClient();

        HttpRequestMessage request = new HttpRequestMessage(
            HttpMethod.Post,
            $"https://directline.botframework.com/v3/directline/tokens/generate");

        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secret);

        var userId = $"dl_{Guid.NewGuid()}";

        request.Content = new StringContent(
            JsonConvert.SerializeObject(
                new { User = new { Id = userId } }),
                Encoding.UTF8,
                "application/json");

        var response = await client.SendAsync(request);
        string token = String.Empty;

        if (response.IsSuccessStatusCode)
        {
            var body = await response.Content.ReadAsStringAsync();
            token = JsonConvert.DeserializeObject<DirectLineToken>(body).token;
        }

        var config = new ChatConfig()
        {
            Token = token,
            UserId = userId  
        };

        return View(config);
    }
}

public class DirectLineToken
{
    public string conversationId { get; set; }
    public string token { get; set; }
    public int expires_in { get; set; }
}
public class ChatConfig
{
    public string Token { get; set; }
    public string UserId { get; set; }
}

And here is a Node controller that does the same:

var router = express.Router(); // get an instance of the express Router

// Get a directline configuration (accessed at GET /api/config)
const userId = "dl_" + createUniqueId();

router.get('/config', function(req, res) {
    const options = {
        method: 'POST',
        uri: 'https://directline.botframework.com/v3/directline/tokens/generate',
        headers: {
            'Authorization': 'Bearer ' + secret
        },
        json: {
            User: { Id: userId }
        }
    };

    request.post(options, (error, response, body) => {
        if (!error && response.statusCode < 300) {
            res.json({ 
                    token: body.token,
                    userId: userId
                });
        }
        else {
            res.status(500).send('Call to retrieve token from Direct Line failed');
        } 
    });
});

 

Custom Direct Line clients

If you have a custom Direct Line client, you can get the same guarantees with a little bit more work on your end. We can talk about that in a future post if there is sufficient interest.

Happy Making!

The Azure Bot Service Team