{"id":334,"date":"2020-06-28T19:03:44","date_gmt":"2020-06-28T19:03:44","guid":{"rendered":"https:\/\/codingwithramin.com\/?p=334"},"modified":"2025-07-11T16:25:02","modified_gmt":"2025-07-11T16:25:02","slug":"implementing-microsoft-teams-authentication-flow-for-tabs","status":"publish","type":"post","link":"https:\/\/codingwithramin.com\/?p=334","title":{"rendered":"Implementing Microsoft Teams Authentication Flow For Tabs"},"content":{"rendered":"\n<p>Most Identity Providers like Azure AD use OAuth 2.0 as an open standard for authentication and authorization, In this blog post I will show you how to implement the OAuth 2.0 flow for <a href=\"https:\/\/docs.microsoft.com\/en-us\/microsoftteams\/platform\/tabs\/what-are-tabs\">Microsoft Teams Tabs<\/a> using SharePoint Framework and the Teams Client SDK. A basic understanding of OAuth 2.0 is required for reading this post, so if you are new to this concept, please <a href=\"https:\/\/medium.com\/@darutk\/the-simplest-guide-to-oauth-2-0-8c71bd9a15bb\">read this  post<\/a> or <a href=\"https:\/\/aaronparecki.com\/oauth-2-simplified\/\">this good overview<\/a> which is easier to follow and understand it.<\/p>\n\n\n\n<p>Source code for this sample is available <a href=\"https:\/\/github.com\/AhmadiRamin\/ms-teams-tab-oauth2-flow\">here<\/a> in Github \ud83d\ude42<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Microsoft Teams Tabs Development<\/h2>\n\n\n\n<p>It&#8217;s important to know that Tabs are simple iframes point to domains declared in the app manifest, iframes do have stronger security, which can be useful for you and for the end use, so if you try to redirect the end user to the login page of a provider like Box.com you will get an error, but fortunately the Teams client SDK provides us a simple way to overcome this issue.<\/p>\n\n\n\n<p>Before implementing the OAuth flow, I want to talk about different ways of developing a Microsoft Teams Tab as each approach has its pros and cons.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">SharePoint Framework<\/h3>\n\n\n\n<p>From SharePoint Framework 1.8, you can build Tabs and host them in SharePoint, so your application will surface using a web part experience, below you can find the pros and cons of using SharePoint Framework to build Tabs:<\/p>\n\n\n\n<p><strong>Advantages<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Automatic hosting<\/li><li>Automatic deployment<\/li><li>Single sign-on (through SharePoint)<\/li><li>CDN optimized<\/li><li>Easy debugging (through workbench)<\/li><li>Access to the SharePoint context<\/li><li>Consume Microsoft Graph API and other Azure AD secured Rest APIs (facilitated by <strong><a href=\"https:\/\/docs.microsoft.com\/en-us\/javascript\/api\/sp-http\/aadhttpclientfactory?view=sp-typescript-latest\">AadHttpClientFactory <\/a><\/strong>class)<\/li><li>Custom properties to configure the tab<\/li><li>Reusability (can be a web part in SharePoint and also a tab in Microsoft Teams)<\/li><\/ul>\n\n\n\n<p><strong>Disadvantages<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Static tabs are not supported yet (but it&#8217;s in their roadmap and is planned to be supported in the future)<\/li><li>Only Tabs supported in a SharePoint framework solution (Bots, Connectors and Messaging Extensions are not supported)<\/li><li>Not easy to add them to a channel or a team programmatically as the URL is dynamic and can be hosted in different SharePoint team site<\/li><li>No server-side code (You can use Azure Functions as server-side code which you have to pay for the service)<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Node.js\/ASP.NET<\/h3>\n\n\n\n<p>You can also create a solution using ASP.Net (MVC or Core) or Node.js and host it in Azure, AWS or in anywhere on the internet. You can also use the <a href=\"https:\/\/github.com\/pnp\/generator-teams\">Yeoman Generator for Microsoft Teams<\/a> which makes it easier for you to develop your Teams solution.<\/p>\n\n\n\n<p><strong>Advantages<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Support all project types such as Bots, Messaging Extensions, Tabs, Connectors and Outgoing Webhooks<\/li><li>Support server-side code (like <a href=\"https:\/\/www.npmjs.com\/package\/express\">Express <\/a>for node.js project)<\/li><li>Support routing<\/li><li>Static page URL<\/li><li>Provisioning is simple<\/li><li>Configurable (through query string parameters for example)<\/li><\/ul>\n\n\n\n<p><strong>Disadvantages<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Manual deployment<\/li><li>Not easy to debug (you have to use <strong>ngrok <\/strong>for node.js projects and pay for a subscription if you don&#8217;t want to change some settings each time you run the project)<\/li><li>SSO is not supported (Yeoman generator is recently added this feature but I haven&#8217;t tried it yet)<\/li><li>You will need a custom domain as Azurewebsite.net domain doesn&#8217;t support for SSO (as it might be a security risk)<\/li><li>Hosting is not free (as it needs to be deployed in a cloud service or a custom hosting)<\/li><\/ul>\n\n\n\n<p>For this project, I will show you how to implement it with SharePoint Framework as it gives us more benefits.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"> Register Your App<\/h2>\n\n\n\n<p>I use Box.com as an example because it has pre-build user interface components such as <a href=\"https:\/\/developer.box.com\/guides\/embed\/ui-elements\/\">UI Elements<\/a> which helps us to focus on the code rather than the UI, but you can use any other providers. The first step is to register your app, each provider has different way of registering the app or even different name, for example Application Registration in Azure or Connected App in salesforce, for Box.com you can navigate to the developer console and select the Create a new app button. You have different options for creating your app such as Enterprise integration or Partner Integration, but let&#8217;s select Custom App and then select Standard OAuth 2.0 (User Authentication).<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth1-961x1024.jpg\" alt=\"\" class=\"wp-image-335\" width=\"600\" height=\"639\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth1-961x1024.jpg 961w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth1-282x300.jpg 282w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth1-768x818.jpg 768w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth1.jpg 1422w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/figure>\n\n\n\n<p>Give it a name and create the custom app. Navigate to the configuration page and here you can find the Client ID and the Client secret, copy the values as we need them later (you may see different names for some providers, for example when you create a connected app in Salesforce, instead of Client ID and Client Secret you will see Consumer Key and Consumer Secret but they do the same thing).<\/p>\n\n\n\n<p>If you scroll down you will find the Application scopes, based on your application you can select or deselect the scopes, for this sample, let&#8217;s stick with the default scope which is <strong>Read and write files and folders stored in Box<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth2-1024x614.jpg\" alt=\"\" class=\"wp-image-336\" width=\"600\" height=\"359\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth2-1024x614.jpg 1024w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth2-300x180.jpg 300w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth2-768x460.jpg 768w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth2.jpg 1515w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/figure>\n\n\n\n<p>We also need to change the Redirect URI and the CORS domains, but we will get back to them later.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create a SharePoint Framework solution<\/h2>\n\n\n\n<p>To create a SharePoint Framework solution (SPFx) please follow this instruction, here is the settings I set for this solution.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"640\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth3-1024x640.jpg\" alt=\"\" class=\"wp-image-339\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth3-1024x640.jpg 1024w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth3-300x187.jpg 300w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth3-768x480.jpg 768w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth3-1536x960.jpg 1536w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth3.jpg 1572w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Install dependencies<\/h3>\n\n\n\n<p>Here are the dependencies we need to install (they are all box-ui-elements dependencies)<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"powershell\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">npm i node-sass axios classnames draft-js form-serialize jsuri pikaday react-animate-height react-intl@2.8.0 react-measure react-process-string react-router-dom react-tether react-textarea-autosize scroll-into-view-if-needed tabbable react-virtualized react-modal js-sha1 @popperjs\/core moment query-string filesize postcss-loader sass-loader box-ui-elements --save-dev<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: User interaction (login in button)<\/h2>\n\n\n\n<p>First we need to build an UI for the user to interact with which is commonly a button called &#8220;<strong>Sign in<\/strong>&#8221; or &#8220;<strong>Log in<\/strong>&#8220;.<\/p>\n\n\n\n<p>I added a function component to the solution to handle the login process:<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"php\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">import * as React from \"react\";\nimport { PrimaryButton } from \"office-ui-fabric-react\";\nimport { boxService } from \"..\/..\/..\/services\/services\";\nimport styles from \".\/BoxContentExplorer.module.scss\";\nimport Loading from \"..\/..\/..\/utilities\/loading\";\nconst loadingImage: any = require(\".\/assets\/loading.gif\");\n\nconst logo: any = require(\".\/assets\/boxLogo.png\");\n\nconst BoxLogin = () => {\n  const [isLoading, setIsLoading] = React.useState(true);\n\n  const openLoginPopUp = async () => {\n    setIsLoading(true);\n    boxService.GetBoxAccessToken()\n    .then(res => setIsLoading(false))\n    .catch(err=>setIsLoading(false));\n  };\n\n  return (\n    &lt;div>\n      &lt;Loading imageSrc={loadingImage} hidden={isLoading} \/>\n      &lt;div className={styles.login} hidden={!isLoading}>\n        &lt;div className={styles.loginContainer}>\n          &lt;img src={logo} alt=\"Box logo\" \/>\n          &lt;p>Please log in to access to this folder&lt;\/p>\n          &lt;PrimaryButton\n            className={styles.loginButton}\n            text=\"Log in\"\n            onClick={openLoginPopUp}\n          \/>\n        &lt;\/div>\n      &lt;\/div>\n    &lt;\/div>\n  );\n};\n\nexport default BoxLogin;\n<\/pre><\/div>\n\n\n\n<p>It would look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"546\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth4-1024x546.jpg\" alt=\"\" class=\"wp-image-343\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth4-1024x546.jpg 1024w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth4-300x160.jpg 300w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth4-768x410.jpg 768w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth4.jpg 1134w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Authentication start page and complete page<\/h2>\n\n\n\n<p>We need two pages, one for redirecting the user to the authorization page of the identity provider to sign in and get consent for the scopes required for our apps, and one for check the returned state and return the code to the success callback function (commonly known as Redirect page and <strong>must <\/strong>be an absolute URI).<\/p>\n\n\n\n<p>You can host those pages anywhere like Azure, AWS or your private host, but for this sample I&#8217;m going to host them in SharePoint, also bear in mind that both pages must be on the same domain otherwise you will get an error.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Start page<\/h3>\n\n\n\n<p>The start page generates random state data, saves it to the local storage for future validation and redirects to the identity provider&#8217;s <strong>\/authorize<\/strong> endpoint which in our sample is https:\/\/account.box.com\/api\/oauth2\/authorize.<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"javascript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">microsoftTeams.initialize();\nmicrosoftTeams.getContext(function (context) {\n    \/\/ Generate random state string and store it, so we can verify it in the callback\n    let state = newGuid();\n    var callbackUrl =\"https:\/\/ramindev.sharepoint.com\/\/SitePages\/loginComplete.aspx\";\n    \n    var clientId = getUrlParameter(\"clientId\");\n    var authorizeUrl = getUrlParameter(\"authorizationUrl\");\n    localStorage.setItem(\"bt.state\", state);\n    localStorage.removeItem(\"simple.error\");\n    \n    let queryParams = {\n      client_id: clientId,\n      response_type: \"code\",\n      redirect_uri: callbackUrl,\n      state: state,\n    };\n    \/\/ Go to the Box.com authorization endpoint.\n    let authorizeEndpoint = `${authorizeUrl}?${toQueryString(queryParams)}`;\n    window.location.assign(authorizeEndpoint);\n});\n<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth7-799x1024.jpg\" alt=\"\" class=\"wp-image-346\" width=\"600\" height=\"768\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth7-799x1024.jpg 799w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth7-234x300.jpg 234w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth7-768x984.jpg 768w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth7-1198x1536.jpg 1198w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth7.jpg 1257w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><figcaption>Sign in page in the identity provider&#8217;s \/authorize endpoint<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth8-1024x926.jpg\" alt=\"\" class=\"wp-image-347\" width=\"600\" height=\"542\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth8-1024x926.jpg 1024w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth8-300x271.jpg 300w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth8-768x695.jpg 768w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth8.jpg 1247w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><figcaption>Grant access to the tab<\/figcaption><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Complete page (Redirect page)<\/h3>\n\n\n\n<p>When the user signed in and granted access to the tab, the provider takes the user to the redirect page with an access token or code, then it checks that the returned state value matches what was saved earlier, and calls the successCallback function if the state is valid and the code is returned, or calls the failureCallback function if something went wrong.<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"javascript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">var code = getUrlParameter('code');\nvar state = getUrlParameter('state');\n\nmicrosoftTeams.initialize();\n\nlet expectedState = localStorage.getItem(\"bt.state\");\nif (expectedState !== state ) {\n    \/\/ State does not match, report error\n    microsoftTeams.authentication.notifyFailure(\"StateDoesNotMatch\");\n} else {\n    \/\/ Success: return token information to the tab\n    microsoftTeams.authentication.notifySuccess(code);\n}\n<\/pre><\/div>\n\n\n\n<p>Now we can upload these pages to SharePoint (I uploaded them in the root site collection) and make sure everyone has access to them.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Update your app&#8217;s configuration<\/h3>\n\n\n\n<p>We need to update the OAuth redirect URI in the app&#8217;s configuration page we setup earlier:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"191\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth5-1024x191.jpg\" alt=\"\" class=\"wp-image-344\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth5-1024x191.jpg 1024w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth5-300x56.jpg 300w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth5-768x143.jpg 768w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth5-1536x287.jpg 1536w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth5.jpg 1831w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>And also the CORS domains:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"233\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth6-1024x233.jpg\" alt=\"\" class=\"wp-image-345\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth6-1024x233.jpg 1024w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth6-300x68.jpg 300w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth6-768x175.jpg 768w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth6-1536x350.jpg 1536w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth6.jpg 1818w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3:Authenticate method<\/h2>\n\n\n\n<p>The Teams client SDK provides a method which opens the start page in an iframe in a pop-up window and you can register the successCallBack and failureCallback function with this method:<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">private Authenticate(\n    ClientId: string,\n    AuthorizationUrl: string\n  ): Promise&lt;any> {\n    const authenticationUrl = `${location.protocol}\/\/${location.hostname}\/sitepages\/${config.authenticatePage}`;\n    return new Promise((resolve, reject) => {\n      const url = `${authenticationUrl}?clientId=${ClientId}&amp;authorizationUrl=${AuthorizationUrl}`;\n      this.teamsContext.teamsJs.authentication.authenticate({\n        url,\n        width: 500,\n        height: 600,\n        successCallback: (result) => resolve(result),\n        failureCallback: (reason) => {\n          this.errorMessage.set(\"Authentication failed, please try again!\");\n          reject(reason);\n        },\n      });\n    });\n  }<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Get access token<\/h2>\n\n\n\n<p>Once we get the authorization code, we need to exchange the authorization code for an access token (the authorization code is only valid for one minute).<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">private async GetAccessToken(code: string) {\n    const data = {\n      grant_type: \"authorization_code\",\n      client_id: config.boxClientId,\n      client_secret: config.boxClientSecret,\n      code: code,\n    };\n    const response = await axios({\n      url: config.boxGetTokenUrl,\n      method: \"POST\",\n      headers: { \"content-type\": \"application\/json\" },\n      data: JSON.stringify(data),\n    });\n    return response.data;\n}<\/pre><\/div>\n\n\n\n<p>Then we can store it local or session storage to avoid this process in each refresh.<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">this.token={\n  AccessToken:tokenObject[\"access_token\"],\n  ExpiresIn:tokenObject[\"refresh_token\"],\n  RefreshToken:tokenObject[\"expires_in\"]\n};\nthis.boxToken.set(this.token.AccessToken);\nlocalStorage.setItem(\n  `${userObjectId}-boxAccessToken`,\n  JSON.stringify(this.token)\n);<\/pre><\/div>\n\n\n\n<p>This token is only valid for certain amount of time (usually 1 hour) and you have to revoke the token, hard coding client secret and keeping the tokens in the client side is not recommended and you should secure them (in another post I will show you how securely keep them in Azure Key Vault and use Azure function to set\/get them), but for this sample I tried to keep it simple and give you the idea of how to implement the flow.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Update the UI with the access token<\/h2>\n\n\n\n<p>Once we get the access token we can make other calls to get data from the resources we are allowed to, in this sample as box-ui-elements gives us everything as a simple component we don&#8217;t need to be worry about the UI, you can simply set the access token and the folder Id, and it renders the UI for you.<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">&lt;ContentExplorer\n  language=\"en-US\"\n  messages={messages}\n  token={boxToken}\n  contentPreviewProps={{\n    contentSidebarProps: {\n      hasActivityFeed: true,\n      hasSkills: true,\n      hasMetadata: true,\n      detailsSidebarProps: {\n        hasProperties: true,\n        hasNotices: true,\n        hasAccessStats: true,\n        hasVersions: true,\n      },\n    },\n  }}\n  rootFolderId={props.folderId ? props.folderId : 0}\n\/><\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"600\" height=\"331\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth11.jpg\" alt=\"\" class=\"wp-image-349\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth11.jpg 600w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth11-300x166.jpg 300w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Deploy the app<\/h2>\n\n\n\n<p>To be able to use this app in Microsoft Teams we need to update the supportedHost in the manifest file:<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">\"supportedHosts\": [\"SharePointWebPart\",\"TeamsPersonalApp\",\"TeamsTab\"]<\/pre><\/div>\n\n\n\n<p>Now we can bundle and upload the package to the app catalog, after upload and deploy the package you can see a button in the ribbon which syncs your app to Teams:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth12.jpg\" alt=\"\" class=\"wp-image-350\" width=\"600\" height=\"576\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth12.jpg 1015w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth12-300x288.jpg 300w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth12-768x738.jpg 768w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/figure>\n\n\n\n<p>Now open Microsoft Teams, go to the Apps, and you can find your app in the organization apps:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth9-1024x495.jpg\" alt=\"\" class=\"wp-image-351\" width=\"600\" height=\"289\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth9-1024x495.jpg 1024w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth9-300x145.jpg 300w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth9-768x371.jpg 768w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth9-1536x742.jpg 1536w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth9-2048x989.jpg 2048w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/figure>\n\n\n\n<p>You can add it as a personal or teams tab.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth10-1024x888.jpg\" alt=\"\" class=\"wp-image-352\" width=\"600\" height=\"520\" srcset=\"https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth10-1024x888.jpg 1024w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth10-300x260.jpg 300w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth10-768x666.jpg 768w, https:\/\/codingwithramin.com\/wp-content\/uploads\/2020\/06\/auth10.jpg 1534w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/figure>\n\n\n\n<p>Here is the final look:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/codingwithramin.com\/img\/posts\/auth-flow-screenshot.gif\" alt=\"\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>What we have seen above is a simple Teams Tab uses the Teams Client SDK to implement the OAuth 2.0 flow. As Teams Tabs are iframes, it&#8217;s not possible to redirect users directly within the tab&#8217;s content, however the Teams Client SDK provides us a safe method to overcome this problem.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most Identity Providers like Azure AD use OAuth 2.0 as an open standard for authentication and authorization, In this blog post I will show you how to implement the OAuth 2.0 flow for Microsoft Teams Tabs using SharePoint Framework and the Teams Client SDK. A basic understanding of OAuth 2.0 is required for reading this post, so if you are new to this concept, please read this post or this good overview which is easier&hellip;<\/p>\n","protected":false},"author":1,"featured_media":341,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[37,39,4],"tags":[38,7,11],"class_list":["post-334","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-microsoft-teams","category-oauth","category-sharepoint-framework","tag-microsoft-teams","tag-sharepoint","tag-spfx"],"_links":{"self":[{"href":"https:\/\/codingwithramin.com\/index.php?rest_route=\/wp\/v2\/posts\/334","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/codingwithramin.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codingwithramin.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codingwithramin.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codingwithramin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=334"}],"version-history":[{"count":8,"href":"https:\/\/codingwithramin.com\/index.php?rest_route=\/wp\/v2\/posts\/334\/revisions"}],"predecessor-version":[{"id":360,"href":"https:\/\/codingwithramin.com\/index.php?rest_route=\/wp\/v2\/posts\/334\/revisions\/360"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codingwithramin.com\/index.php?rest_route=\/wp\/v2\/media\/341"}],"wp:attachment":[{"href":"https:\/\/codingwithramin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=334"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codingwithramin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=334"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codingwithramin.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=334"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}