This article demonstrates how to create a Spring Boot web app in Java that replicates the functionality of the Channel and group tab with SSO quick-start sample app on GitHub.
This is the second article in a three-part series demonstrating how the Microsoft Teams sample library can be used to jump-start building your own Java-based Teams applications. In this article, I’ll show how to make a channel (or group) tab secured with a single sign-on (SSO). However, most of the setup and installation operations for this application are the same as the personal tab application, so you’ll want to review the first article in this series before proceeding.
Microsoft provides a great Teams apps overview. They discuss the difference between personal tab apps and channel tab apps, which is that channel tab apps deliver content to a group of Teams users instead of the single user. For this reason, the application detailed below is an extension of the application in my previous article.
Microsoft also points out that channel tab applications require a configuration page to control what group users can do within the app. They provide an excellent article on creating configuration pages.
The application I’ll build here provides a simple configuration page as an example of communicating with Teams to control group settings. You'll need Java experience to follow along, but I’ll explain how to use Teams.
Implementing SSO in a Teams Application
To implement the single sign-on (SSO) in a Teams application, we first register the application with Azure Active Directory in the same tenant that hosts Teams. The Teams Tab SSO Authentication README page covers this registration process. For additional details, review the "Implementing SSO in a Teams Application" section of the first article.
Creating the Application Using Spring Initializr
After you have completed the application registration, you are ready to build it. Spring provides the Spring Boot Initializr tool, which creates a Maven package that we can import into Eclipse.
Run the tool by opening a browser and going to https://start.spring.io/. Then, fill out the project details and metadata. Include the following dependencies:
- Spring Web
- Thymeleaf
- Azure Active Directory
- OAuth2 Client
- Spring Boot DevTools
Click Generate to create a ZIP file that provides the complete Maven infrastructure and the initial runtime class. Unzip the resources into your project directory and import them into Eclipse as an existing Maven project.
In Eclipse, select File > Import to display this dialog:
Select Existing Maven Projects and click Next to display this dialog:
Select the directory to which you unzipped the file downloaded from Spring Initializr, check the box next to your project and click Finish.
After you import the project, enable Bootstrap CSS by adding this dependency to the pom.xml file:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.0.0-2</version>
</dependency>
We need to make additional changes to the web pages, but I’ll cover that later.
This application’s complete source is available on GitHub.
Preparing the Core Java Code
As in the first article, I removed as much code as possible from the sample application to highlight the code we need to implement SSO. The following Java classes derive from the corresponding C# files.
Java Class File | C# Class Source File |
AppConfiguration.java | Startup.cs |
ChannelTabSsoApplication.java | Program.cs |
HomeController.java | HomeController.cs |
AuthController.java | AuthController.cs |
SSOAuthHelper.java | SSOAuthHelper.cs |
When Spring Boot starts our application, it runs main
in the ChannelTabSsoApplication
class. This class uses the default class implementation that the Spring Boot starter provides.
The AppConfiguration
class configures the Spring Boot engine. We must remember that Teams launches our application in an iframe, and Azure App Service’s web application server enforces cross-frame scripting security. So, we need to add some code to allow our application to run:
package com.contentlab.teams.java.channeltabsso;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@Order(value = 0)
public class AppConfiguration extends WebSecurityConfigurerAdapter
{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.contentSecurityPolicy(
"frame-ancestors 'self' https://*.microsoftonline.com https://*.microsoft.com;"
);
}
}
Mozilla states that the content-security-policy
header obsoletes the X-FRAME-OPTIONS
header, so I’ve implemented the newer method in this application.
The HomeController provides mappings and handlers for the index
page, the configure
page, and the /Auth/Start
and Auth/End
URLs that the SSO process needs. I annotate this class with @Controller because the response contains web pages.
The AuthController class provides mapping and a handler for the GetUserAccessToken
URL. I annotate this controller with @RestController
, which provides a text response to the caller. Code in auth.js fetches the URL when the SSO sequence exchanges the user ID token for the user access token.
The SsoAuthHelper
class provides the GetAccessTokenOnBehalfUser
, which the AuthController.GetUserAccessToken
calls. The GetAccessTokenOnBehalfUser
method handles accessing Teams resources on behalf of the authenticated user. This application uses the same process that we covered in detail in the previous article.
Preparing the Web Resources
I implement the client code in three HTML files:
Java Application HTML File | C# HTML Source File |
index.html | Home/index.cshtml |
/Auth/Start.html | Auth/Start.cshtml |
/Auth/End.html | Auth/End.cshtml |
The index.html web page has a link to auth.js. Auth.js contains a ready
event handler that triggers as soon as possible after the file is loaded. The code for the handler is shown below:
$(document).ready(function () {
microsoftTeams.initialize();
getClientSideToken()
.then((clientSideToken) => {
console.log("clientSideToken: " + clientSideToken);
return getServerSideToken(clientSideToken);
})
.catch((error) => {
console.log(error);
if (error === "invalid_grant") {
$("#divError").text("Error while exchanging for Server token - invalid_grant - User or admin consent is required.");
$("#divError").show();
$("#consent").show();
}
});
});
The Microsoft Teams API is initialized and getClientSideToken
, which is implemented in auth.js, is called to authenticate the user. If that succeeds, getServerSideToken
is called to exchange the client token for a Teams token.
For this application, the page displays information to verify that the user is authenticated and that the application has access to Teams resources. If authentication fails — because the current access token times out, perhaps — it provides a button enabling the user to trigger the authentication process.
The application also has supporting image files: IconRed.png, IconGray.png, and TeamsIcon.png.
Adding the Supporting JavaScript
As in the previous article, I lifted one JavaScript file, auth.js, intact from the Microsoft sample application. I covered the functions in this file that initiate and handle the OAuth 2 flow in detail in the previous article.
Deploying the Application
Now that we have all the application’s parts, we need to build and deploy it. Maven provides the tools to create an Azure App Service plan where we can develop and deploy the application. To use these tools, first, open a command-line window. Next, change the directory (cd
) to the project directory containing the pom.xml file. Then, execute these commands:
az login --tenant << your tenant ID >>
mvn com.microsoft.azure:azure-webapp-maven-plugin:2.2.0:config
mvn package azure-webapp:deploy
Where << your tenant ID >>
is, the ID Azure Active Directory assigns to your tenant. You can find it on the Overview page of your Default Directory when you open the portal page for the Azure Active Directory service.
These functions, which I covered in detail in the previous article, do the following. First, the az login
command logs you into Azure so that Maven can create resources. The first mvn
command captures the information to make the Azure App Service plan, and the second mvn
command deploys the application to that plan.
Fixing up the App Registration and application.properties File
Now that you have deployed the application, you need to update the information in the Azure Active Directory App Registration portal. There are four parts to this process: capturing the App Service URL, updating the App Registration information, revising the application.properties file, and redeploying the application.
You can capture the new Azure App Service URL from the deployment process’ text response:
[INFO] Successfully deployed the artifact to https://channeltabsso-1634231313958.azurewebsites.net
In the previous article, I pointed out that you need to update the hostname in the Redirect URL and the Application ID URL to match the hostname in the URL from the Azure App Service. Screenshots are available in that article to guide you if you need help.
The application.properties file format is below. You’ll need to replace << Your Tenant ID >>
and other placeholders with the IDs you used when registering your application.
# Specifies your Active Directory ID:
azure.activedirectory.tenant-id=<< Your Tenant ID >>
# Specifies your App Registration's Application ID:
azure.activedirectory.client-id=<< application ID >>
# Specifies your App Registration's secret key:
azure.activedirectory.client-secret=<< your app secret >>
azure.auth.url=/oauth2/v2.0/token
azure.instance=https://login.microsoftonline.com
azure.api=api://<< Registered hostname >>/<< your client ID >>
azure.scopes=https://graph.microsoft.com/User.Read
azure.valid.issuers="https://login.microsoftonline.com/<< Your tenant ID >>/v2.0,https://sts.windows.net/<< your tenant ID >>/"
spring.thymeleaf.prefix=classpath:/templates/
When you finish replacing these IDs, you can redeploy the application.
Testing in Teams
The manifest file for this application is below. The critical element that makes this a group application instead of a personal application is the scopes
object in the configurableTabs
object, with the team
and groupchat
values.
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.11/MicrosoftTeams.schema.json",
"manifestVersion": "1.11",
"version": "1.0.0",
"id": "<< any random GUID >>",
"packageName": "com.contentlab.teams.java.channeltabsso",
"developer": {
"name": "Microsoft",
"websiteUrl": "https://www.microsoft.com",
"privacyUrl": "https://www.microsoft.com/privacy",
"termsOfUseUrl": "https://www.microsoft.com/termsofuse"
},
"name": {
"short": "Java Group Tab Teams Auth SSO",
"full": "Java Group Tab Teams Auth SSO"
},
"description": {
"short": "Java Group Tab Teams Auth SSO",
"full": "Java Group Tab Teams Auth SSO"
},
"icons": {
"color": "color.png",
"outline": "outline.png"
},
"accentColor": "#0000EE",
"configurableTabs": [
{
"configurationUrl": "https://channeltabsso-1634231313958.azurewebsites.net/Configure",
"canUpdateConfiguration": true,
"scopes": ["team", "groupchat"]
}
],
"permissions": ["identity","messageTeamMembers"],
"validDomains": [
"channeltabsso-1634231313958.azurewebsites.net",
"*.onmicrosoft.com",
"*.azurewebsites.net"
],
"webApplicationInfo": {
"id": "a2f5e45f-c3a7-4ba6-8e14-543e45e1be95",
"resource": "api://channeltabsso-1634231313958.azurewebsites.net/<< your client id >>"
}
}
Build the manifest.zip file as I described in the first article. Then upload the file to Teams. When you add the application to Teams, you’ll see this:
Select a channel and click Set up a tab.
You may need to wait for the Azure App Service to start. Look for a status message in the lower-left corner:
When the application loads, you’ll see the Configure tab:
This sample application just shows the authentication data. You can click the red and gray buttons to change the configuration. After you select a color, the application enables the Save button. Since this is just a demonstration application, the button event handlers are just placeholders that exist to show how to enable the Save button and post a notification to Teams that the configuration has been saved.
<script>
microsoftTeams.initialize();
let saveGray = () => {
microsoftTeams.settings.registerOnSaveHandler((saveEvent) => {
microsoftTeams.settings.setSettings({
websiteUrl: window.location.origin ,
contentUrl: window.location.origin + "",
entityId: "Java Channel Configure",
suggestedDisplayName: "Java Channel Configure - Grey Settings"
});
saveEvent.notifySuccess();
});
}
let saveRed = () => {
microsoftTeams.settings.registerOnSaveHandler((saveEvent) => {
microsoftTeams.settings.setSettings({
websiteUrl: window.location.origin ,
contentUrl: window.location.origin + "",
entityId: "Java Channel Configure",
suggestedDisplayName: "Java Channel Configure - Red Settings"
});
saveEvent.notifySuccess();
});
}
let icon = document.getElementById("icon");
const colorClickGray = () => {
microsoftTeams.settings.setValidityState(true);
saveGray()
}
const colorClickRed = () => {
microsoftTeams.settings.setValidityState(true);
saveRed();
}
</script>
Your application will need to present choices relevant to the types of interactions your application supports. This might include selecting a conversion category, setting the conversation title, or, if you implement an ad-hoc approval workflow, naming the approvers and specifying the approval order.
When you click the Save button, the application’s main page is presented. What your application displays here will depend on your application and the captured configuration data.
Next Steps
You now have the shell for a working channel tab application. I’ve covered all the details of integrating your application with Teams and securing it with SSO. You need to add the configuration details and the group application, but that’s the standard practice for building web-based single-page applications that you already know.
As you experienced, converting a Teams application from C# to Java takes minimal effort. I just changed one function in SsoAuthHelper
. The rest of the HTML and JavaScript that SSO requires carries over directly. Your Java skills are relevant, and you can continue to use your familiar tools to create Teams apps.
This article has also provided a process for converting Microsoft’s C# samples. So, you can browse through the library and find other examples to give you a starting shell.
Continue to the final article of this series to explore creating a conversational tab app.