Extend your Teams environment with custom functionality; web parts chapter II

Extend your Teams environment with custom functionality; web parts chapter II

Reinder Wit

Reinder Wit

In my previous post, I demonstrated how to create custom web parts for use within your Sharepoint environment. With a few minor configuration adjustments, you can make these web parts work within your Teams environment as well!

In this post, I’ll be looking into creating a Sharepoint web part that retrieves data from an API (actually an Azure Function app) that is secured using Azure Active Directoy (AAD) and showing the data returned from that API in either a tab in your Teams environment or as a so-called ‘personal app’ in Teams.

Contents of this blog post:

Requirements
To create a web part on your machine, the following software has to be installed on your machine:
– Node.js
– gulp, globally installed
– a code editor (I use Visual Studio Code)
Yeoman (to install, run this command: npm install -g yo)
– a working SharePoint site, preferably with admin rights otherwise you won’t be able to deploy the web part in the end
– an API running on the Azure platform, or any API that can be secured using AAD

1. Setup AAD authentication

In my previous post about web parts, I showed you how to get access to a Sharepoint API, like the Lists API. Allthough this might be useful for some web parts, most of the times you’ll have to connect with API’s outside of Sharepoint. For this blog post I want to demonstrate how to connect to an API that is secured using Azure Active Directory. As web parts are not really a good place to store your secrets it’s much more practical to let AAD handle authentication; this basically allows you to connect to any app service or function app that you have running in Azure immediately without much configuration.

My API
I’ve created a very basic Function App as an example, with an HttpTrigger that returns some JSON. Each object in the JSON describes a (fictional) person with a random picture:

random persons json

Enable AAD authentication
For your app service or function app, browse to the ‘Authentication / Authorization’ blade and set the authentication toggle to ‘On’. In the dropdown that appears, select ‘Log in with Azure Active Directory’ and then click the ‘Azure Active Directory’ authentication provider underneath.
In the next window, select the ‘Express’ mode and enter a name for your authentication app:

new authentiction app

Click ‘OK’ at the bottom and when returned to the blade, hit ‘Save’ to save the changes. The authentication provider for AAD should now say Configured (Express Mode: Create). Refresh the page (F5); the AAD provider now says Configured (Express: Existing app). Click it again to display the new settings and then click the app name (‘random-persons-aad-app’, in my case). You will see a window similar to this:

authentication client app name and id

Copy the ‘App Name’ and ‘Client Id’ values for later use.
When this is all done, verify that your API returns a 401 Unauthorized now that AAD is configured for authentication, for instance by making a request using a tool like PostMan.

CORS
You will also have to enable CORS for your API, allowings for requests to the API from your Sharepoint URL. I’ve added https://reinderw.sharepoint.com to the CORS settings for my Function App in Azure.

2. Create the web part

Fetch the Sharepoint web part sample project by running this command in a folder on your machine (Check out my previous post for more info):

yo @microsoft/sharepoint

Once this command is finished, open the newly created project in a code editor, like Visual Studio Code.

Configurations for Teams
First of all, we have to let Sharepoint know that this web part can be used in Teams. We do this by adding new supportedHosts values in our [YourWebPartName].manifest.json:

"supportedHosts": ["TeamsPersonalApp","TeamsTab"],

Next, we need to configure the correct API permissions in file config/package-solution.json. This is the location where we need to use the app name of our authentication client we created in step 1.
Important: to get the API access approval step to work correctly (step 4), we need to add an additional setting for Azure Active Directory as well:

"webApiPermissionRequests": [
  {
    "resource": "random-persons-aad-app",
    "scope": "user_impersonation"
  },
  {
    "resource": "Windows Azure Active Directory",
    "scope": "User.Read"
  }
],   

This JSON file is also the place where you can specify additional information about the web part: locate the developer property and add the necessary fields if needed.

When the Teams interface displays your web part, it also shows an icon. There are 2 icons available for Teams: a regular one and a transparent one for use in the left navigation. These icons are located in the teams folder in the root of the project and you can overwrite these with files of your own, provided that they have the same name as the original files.

Web part customizations
As we will be using the AAD HttpClient we need to import some stuff from the Sharepoint HTTP libraries. Put this at the top of your web part TypeScript file:

import { AadHttpClient, HttpClientResponse } from '@microsoft/sp-http';

Within your web part class, add the onInit method and use the aadHttpClientFactory to setup your API client. Use the client ID from the previous step as the parameter for getClient:

export default class SentiaInTeamsWebPart extends BaseClientSideWebPart<ISentiaInTeamsWebPartProps> {
  private personsClient: AadHttpClient;

  protected onInit(): Promise<void> {
    return new Promise<void>((resolve: () => void, reject: (error: any) => void): void => {

      this.context.aadHttpClientFactory
        .getClient('32318e9f-6321-45d5-ab51-20874dc095f3')
        .then((client: AadHttpClient): void => {
          this.personsClient = client;
          resolve();
        }, err => reject(err));

    });
  } 

  ...

Replace the render method with something similar to this (don’t forget to change the API URL and change the class name of the first <div> to use your web part name):

  public render(): void {
    this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'data');

    this.personsClient
      .get('[your API URL]', AadHttpClient.configurations.v1)
      .then((res: HttpClientResponse): Promise<any> => {
        return res.json();
      })
      .then((persons: any): void => {
        this.context.statusRenderer.clearLoadingIndicator(this.domElement);
        this.domElement.innerHTML += `
        <div class="${ styles.sentiaInTeams }">
          <ul>
            ${persons.map(o => `<li><img src="${o.picture}"/><p><strong>${o.name}</strong><br/>${o.birthDate}<br/>
            <a href="#">${o.email}</a></p></li>`).join('')}
          </ul>
        </div>`;
      }, (err: any): void => {
        this.context.statusRenderer.renderError(this.domElement, err);
      });

  }

In short, the code above first displays a loading indicator, then my API client is used to call the API and retrieve the random persons in JSON format and finally the loading indicator is removed and the JSON response is transformed into a list.

And here are the contents of my SASS file (my ‘sentiaInTeams’ base class will have a different name in your SASS file):

@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';

.sentiaInTeams {
  ul {
    margin:0;
    padding:0;
    li {
      display:inline-block;
      width:250px;
      height:350px;
      list-style:none;
      margin:0 30px 30px 0;
      background-color: $ms-color-themePrimary;
      color:$ms-color-white;
      font-family:'Trebuchet MS', 'Lucida Sans Unicode', Arial, sans-serif;
      a {
        color:$ms-color-white;
        text-decoration:underline;
      }
      p {
        margin:0;
        padding:10px 15px 15px;
        line-height:1.3em;
      }
      img {
        width:100%;
      }
    }
  }
}

3. Deploy to Teams

When you’re all done with customizing the web part, it’s time to deploy it to Teams. Run the following commands to build and package the project:

gulp bundle --ship
gulp package-solution --ship

This will create a file sharepoint/solution/[your solution].sppkg which we need to upload to Sharepoint.

Deploy the file to your Sharepoint app catalog; check out these steps in my previous blog post about web parts on how to do this.
Once you have uploaded the package file, select it and click the FILES tab at the top navigation bar. In the ribbon that appears, click the Sync to Teams button and wait for the notification to appear:

Sync to Teams

Now go to your Teams environment and click on the Apps icon in the bottom left corner. Click on your web part and click the ‘Open’ button. Wait for your web part to be loaded:

No consent exception animated

Your web part will fail to load but this is expected. The exception will state something like ‘The user or administrator has not consented to use the application with ID ‘177c71fc-1022-4e3c-82cd-faa17d9864bf’ named ‘SharePoint Online Client Extensibility Web Application Principal’. Send an interactive authorization request for this user and resource.
It is because we still have to approve the use of our API through AAD, which is the next and final step.

4. Approve API access request

As an extra security measure, you need to approve the request to connect to your API from within Teams using the Sharepoint SPFX framework.

Go to your Sharepoint Admin center (https://[your site]-admin.sharepoint.com) and click Advanced > API access in the left menu; wait for a few seconds for the approval requests to be loaded and then approve both your client app and the AAD request:

Approve requests

(Note: these approvals end up as App Registrations in your AAD in Azure)

When you’ve approved the requests, reload or re-open your web part in Teams and it should now work!
It might take a couple of minutes for all the changes to take effect…

add to teams animated

References

Reinder Wit
Reinder Wit

Senior .NET developer