with Ben Kelly and Dean Ward
These days, everything in your life is connected. Your email connects to your phone, which connects to your garage door, which talks to your security system. It’s no different with the apps you use to get work done; everything needs to integrate smoothly, allowing you to quickly access relevant data from whatever tool you’re using at the moment. We have limited attention; anything not easily accessible in our workflow isn’t going to keep your attention for very long.
For most of us today, much of our work, especially communication and coordination with colleagues, takes place in a chat app.
Our Teams product is a great way to store, search, and rank the knowledge co-workers exchange through questions and answers. But many of those conversations still take place in chat, which means we’re most useful to customers when our tool plays nice with those chat apps. We integrated with Slack already, but not everybody uses that. We needed to integrate with Microsoft Teams (from here on out, MS Teams) as well.
Below, we’ll discuss how we approached and accomplished the integration between SO for Teams and MS Teams. This is a technical blog for folks who are interested in the process of how we got these two codebases to play nice with one another. If you want to read more about the integration feature set, check out the product announcement blog.
Calculating the Initial Approach
Because SO for Teams is an integrated part of the central Stack Overflow code base, our integration with MS Teams would be included in that codebase. StackOverflow.com and our Stack Exchange network of sites get hundreds of millions of hits every month, so we’re very focused on speed and performance. And we’re always wary about taking on any new dependencies.
Microsoft offers a very robust SDK for its Teams integrations. It does a lot and would have made the integration very easy, but it would have added a lot of third-party dependencies to our codebase. It requires the integration to create middleware to integrate into our pipeline. On a new pipeline in a new project, that’s no problem. On a ten-year-old pipeline with a lot of customizations, it becomes a little more of a problem. We’ve done a lot of work with that pipeline to cache data through Redis and make every answer show up fast. But dependencies could allocate data without us always knowing about it.
Instead, we went with their REST API, which is more barebones. It requires a lighter lift, so that made our decision for us. Because of that, we ended up duplicating things that existed in the SDK. We weren’t completely starting from scratch—we were able to reuse some work from our Slack integration. Plus, REST is what we are used to. In some ways, it’s easier than trying to learn the ins and outs of a new SDK.
Once that decision was made, it was time to build the bridges between one Teams and the other.
Getting Stack Overflow into MS Teams
Once we knew which tools we would use, we could start developing the integration. The install process was an interesting challenge because there were a lot of ways we could approach it. We could have sent users to our website to initiate the install, but from their perspective, it made more sense to have the entire process stay within MS Teams. The people you want to reach are already in that app, so why make them leave to install the integration?
Our installation operates with the standard process that most MS Teams apps use. A user initiates the install from their store, which launches a MS Teams installer. The installer has a series of install screens, the first of which comes from MS Teams, while all of the remaining screens come from our codebase. The install walks through the following steps:
- Ensure that the integration has not already been installed.
- Display the standard Stack Overflow login screen to authenticate so we know what SO Team they belong to.
- Now that we know both their MS Team account and SO for Teams account, we double check that these two accounts are not already associated by reading from a database table, then link the MS Teams tenant/user (the specific user within the MS Teams instance) and the Stack Overflow team/user.
- For the first user to install, this requests a Stack Overflow Teams admin to configure the integration.
- For every user who goes through this process after the admin has configured the integration, once the user has successfully gone through the authentication process above (to prove they are a member of the Stack Overflow team), the bot sends a message that says that the integration is ready.
Everyone who wants to use SO for Teams within MS Teams has to go through the install process once, no matter how many other people have already completed it. That’s because the key part of this process is linking the Microsoft Teams tenant/user with the Stack Overflow team/user.
Keeping Team Data Secure
We spent a lot of time making sure that the security was tight. We’re dealing with companies’ internal conversations and proprietary information, so it’s very important to make sure that’s locked down. The first security gate comes when we require an admin of the SO for Teams instance to authorize the app in order to allow any user to use it. That’s because a user who is part of multiple SO for Teams might paste a link into MS Teams that might not be allowed for users in that conversation (or entire MS Teams instance).
After consulting with our internal experts, we realized that we didn’t need to build out a full OAuth2 process—our internal auth would work fine. But for any conversation to happen, a user still has to be authorized on both sides, no matter what authentication method the organizations use for MS Teams and SO for Teams, whether it’s SAML, OAuth, or something else. They just don’t have to perform authentication between the systems.
Then, to secure every request, whether it’s to unfurl a link, perform a search, or notify a user of new questions and answers, it has to be validated against a JWT token. But because we use the REST API, we had to decompose this process from their SDK and just use the bits that we needed.
The message auth above highlights a difference between MS Teams and our Slack integrations. In Slack, we authenticated with their app. In MS Teams, they authenticate every payload with us. It behaves as if they are using our API instead of vice versa. It was backwards to what we previously implemented, so it required a ground-up redesign of our chat integration logic. And because we were the ones doing the authorization, we had to be a little more careful about security. From the user’s perspective, though, the process looks almost exactly the same.
We added a couple of extra security features for this integration to ensure all proprietary data stayed safe and did not leak out. The initial login screen comes from the public SO site. But every SO for Teams instance is stored on a separate, segregated server. We have no idea which SO Team a user is a part of until they login, then we can associate the Microsoft Teams user with the Stack Overflow Teams user.
Second, we want to make sure that absolutely nothing vital leaked out accidently. On all Q&A sites, public and private, you click on a tag to see all the questions associated with it. On the public sites, that tag goes in the URL, and that URL ends up in the server logs, which, for SO for Teams, could be a leak of information. So for SO for Teams, we encrypt all tags before they get to the URL, even though it’s internal to us.
Designing Bots for Teams
With the integration installed, a given MS Teams user has a bot added to their view. They could treat this bot as a person and request information, say, “Hey, how do I exit vim?” The bot would pass an Activity object and the search query to our endpoint, which would return the best matching question.
But that only solved part of our problem. We want people to be able to monitor tags so that the questions and answers that they are interested in come to them. For the most part, MS Teams assumes conversations are two-way and initiated by the user. Which meant that we couldn’t pass information to the user unless they initiated a conversation—at that point, we would get the identifiers for the channel. Instead, we could use proactive messaging, which allowed a bot to initiate, but that required a ton of information from the bot to get started. To get this proactive messaging from the outset, we had to go back and change the installer to store that information when the user initially installed the integration.
To create the actual links between a tag that you want to monitor and the MS Teams user, we use custom webhooks. MS Teams lets integrations embed configuration pages (known as Tabs; more on this below) into the program. We use these pages to let the user configure a webhook that will receive incoming questions and answers related to the tags that they care about. There, they could determine whether they want questions, answers, and/or comments for a tag sent to a given channel.
Keeping Tabs on Teams
Tabs, while powerful tools to configure our integration within MS Teams, almost didn’t make it into our initial build because of the additional complexity they added. A Tab is essentially an iframe loaded in MS Teams, either in a channel or on an individual user, to configure something for that chat location. So the channel tab would configure notifications for a single channel, and the user tab would configure personal notifications for a single user. The Stack Overflow site serves up the content of the iframe; it was the same settings page as you would see using SO Teams.
But we weren’t on the SO site here; we were within the MS Teams application and had to match its look and feel. Microsoft provided those styles as TypeScript and a build script that pulled those styles out into a standard CSS file. The issue was that the script didn’t pull everything out. So we had to tweak it a little here and there. The short of it is, yes, we support dark mode within MS Teams.
Our tabs primarily create/edit/delete the webhook notifications used to send new questions and answers to channels and users. Unfortunately, tabs have even less context than bots, so integrating with those challenges was extra difficult. MS Teams walls off some of the API functionality from tabs for a very good reason—you don’t want a random webpage in your app accessing the same data as an approved and vetted integration. But our tab still needed some of this information.
To pull this off, we cached some extra information in the installation, specifically the conversationUpdate event. When the user installs a tab, we tie this event to them and the tab. If the user has already authorized with the bot, great, we got the info we need. If the user hasn’t done that and we don’t have the cached info, we just ask the user to sign in with the bot again and set an account cookie. It’s something of a pain for the user to sign in again, but the more deliberate gates there are to prevent leaking information, the better.
Speaking of security, because tabs iframe out to our site, we needed to add additional precautions on the iframe content. Generally, we disallow iframes of our content globally, so just to enable this, we had to set up specific response headers for specific routes. To verify the user, we couldn’t just use the identifiers within either app; technically, those could be spoofed. We couldn’t validate against the JWT like we do with bot requests, tab can’t access that. So we use the cookie we mentioned above to validate. And if that user was anonymous when they sent the request, we send them to log in again, and we get the cookie in place.
We’ve Teamed Teams with Teams
Our customers had been asking for this for a while, but Microsoft ended up contacting us and providing a recommended feature set. In fact, Microsoft was incredibly helpful throughout the whole process. Initially, they gave us a weekly fifteen minute call with their engineers, but that meant sometimes we’d be blocked until the call. To fix this, they eventually gave us an entire MS Teams instance in which to ask questions of their engineers and get the project completed as quickly as possible. We’d be blocked on something for hours, sometimes for days, and within a few minutes of posting in this MS Teams instance, our Microsoft engineering contact was able to unblock us in a matter of minutes.
On top of that, they gave us free access to MS Teams for 90 days so that we could invite anyone we wanted to test the integration thoroughly. Microsoft went above and beyond during this whole integration—we wouldn’t have been able to do it without them.
Our integration with Microsoft Teams continues our mission to situate your knowledge management process within your existing workflow. Chat programs are central to our work processes, so we want to be available in the tools that they use.
Again, we couldn’t have gotten this integration completed as quickly as we did without Microsoft’s help. They gave us a ton of support and helped us get this project through the process faster than we could have alone.
New to Stack Overflow for Teams? Now it's free for your first 25 users. Try Teams for free today and make internal knowledge sharing and information discovery even easier with our new integration.