{"id":186,"date":"2017-11-16T00:00:00","date_gmt":"2017-11-16T00:00:00","guid":{"rendered":"http:\/\/officedevblogs.wpengine.com\/?p=186"},"modified":"2017-11-16T00:00:00","modified_gmt":"2017-11-16T00:00:00","slug":"authentication-in-microsoft-teams-apps-tabs","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/authentication-in-microsoft-teams-apps-tabs\/","title":{"rendered":"Authentication in Microsoft Teams Apps: Tabs"},"content":{"rendered":"<div id=\"body-content\">\n<h2>Introduction<\/h2>\n<p>Some of the most common questions we receive from Microsoft Teams developers concern authentication to Azure Active Directory (Azure AD), single sign-on (SSO) to Azure AD, and how to access Microsoft Graph APIs from within a Microsoft Teams app. Here, we&#8217;ll explain in detail how to do these things, going above and beyond <a href=\"https:\/\/msdn.microsoft.com\/en-us\/microsoft-teams\/auth\">authentication basics<\/a>.<\/p>\n<p>The TypeScript source code shown here can be found in our comprehensive sample apps: <a href=\"https:\/\/github.com\/OfficeDev\/microsoft-teams-sample-complete-node\">https:\/\/github.com\/OfficeDev\/microsoft-teams-sample-complete-node<\/a>. There&#8217;s also a <a href=\"https:\/\/github.com\/OfficeDev\/microsoft-teams-sample-complete-csharp\">C# version<\/a>.<\/p>\n<h2>First Things First<\/h2>\n<p>Don&#8217;t rely on the information from tab context to establish the user&#8217;s identity, whether you get it as URL parameters to your tab content URL or by calling the microsoftTeams.getContext() function in the Microsoft Teams client SDK. A malicious actor could invoke your tab content URL with its own parameters, and a web page impersonating Microsoft Teams could load your tab content URL in an &lt;iframe&gt; and return its own data to the getContext() function. You should treat the identity-related information in the tab context as \u00e2\u20ac\u0153hints,\u00e2\u20ac\u009d and validate them before use.<\/p>\n<h2>The Basics<\/h2>\n<p>The help topic <a href=\"https:\/\/msdn.microsoft.com\/en-us\/microsoft-teams\/auth\">Authenticate a user in your Microsoft Teams tab<\/a> covers the basics of tab authentication. Here I&#8217;ll present a working example of a static tab that requests an access token for Microsoft Graph and shows the current user&#8217;s basic profile information from Azure AD.<\/p>\n<h2>Configuring your Azure AD Application<\/h2>\n<p>To authenticate with Azure AD, you will need to register an application and configure it correctly. If your Microsoft Teams app has a bot or a compose extension, an Azure AD application was created for you when you registered your bot with the Bot Framework. If your Microsoft Teams app just has a tab, you can create a new application through the Application Registration Portal (<a href=\"https:\/\/apps.dev.microsoft.com\">https:\/\/apps.dev.microsoft.com<\/a>).<\/p>\n<p>The examples below use the <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/active-directory\/develop\/active-directory-dev-understanding-oauth2-implicit-grant\">OAuth2 Implicit Grant flow<\/a> via the V1 endpoint, and reads the user&#8217;s profile information. Here&#8217;s how to configure your application:<\/p>\n<p>1. Open the <a href=\"https:\/\/apps.dev.microsoft.com\/\">Application Registration Portal<\/a>, locate your app, and click on it to see its properties.<\/p>\n<p>2. For the <a href=\"https:\/\/github.com\/OfficeDev\/microsoft-teams-sample-complete-node\">TypeScript\/Node.js<\/a> and <a href=\"https:\/\/github.com\/OfficeDev\/microsoft-teams-sample-complete-csharp\">C#<\/a> sample apps on GitHub, the redirect URLs will be similar to what&#8217;s below, but the hostname will be different. That is, the redirect URLs for the sample app will be https:\/\/<em>yourhost<\/em>\/tab-auth\/simple-end and https:\/\/<em>yourhost<\/em>\/tab-auth\/silent-end.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/05\/msft20teams20image201.png\" alt=\"User.Read delegated permission, which newly-created apps will have by default\" width=\"740\" height=\"288\" \/><\/p>\n<p>3. In the &#8220;Microsoft Graph Permissions&#8221;\u009d section, add the permissions your app requires. The examples below assume the User.Read delegated permission, which newly-created apps will have by default.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/05\/msft20teams20image202.png\" alt=\"TypeScript\/Node.js and C# sample apps on GitHub, the redirect URLs\" width=\"740\" height=\"242\" \/><\/p>\n<h2>Step 1: Initiate Authentication Flow<\/h2>\n<p>In the tab content or configuration page, call the microsoftTeams.authenticate() function of the Microsoft Teams client SDK to launch a popup that will host the authentication flow.<\/p>\n<pre>microsoftTeams.authentication.authenticate({\n\u00a0\u00a0\u00a0 url: window.location.origin + \"\/tab-auth\/simple-start\",\n\u00a0\u00a0\u00a0 width: 600,\n\u00a0\u00a0\u00a0 height: 535,\n\u00a0\u00a0\u00a0 successCallback: function (result) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 getUserProfile(result.accessToken);\n\u00a0\u00a0\u00a0 },\n\u00a0\u00a0\u00a0 failureCallback: function (reason) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 handleAuthError(reason);\n\u00a0\u00a0\u00a0 }\n});<\/pre>\n<p>&nbsp;<\/p>\n<p>Here are some important things to understand about the above function:<\/p>\n<ul>\n<li>Identity providers typically don&#8217;t allow their login and consent pages to be placed in an &lt;iframe&gt;, so you must use a popup (such as \/tab-auth\/simple-start above) instead of trying to host the login experience directly in your tab.<\/li>\n<li>The URL you pass to microsoftTeams.authenticate() is the start page of your authentication flow. This page must be on a domain that&#8217;s in your validDomains list, or else the popup will not open.<\/li>\n<li>The authentication flow must start on a page that&#8217;s on your domain; don&#8217;t start it directly to your identity provider&#8217;s login or consent page. In our example, even though we&#8217;re using Azure AD, we begin at \/tab-auth\/simple-start rather than going directly to the Azure AD endpoint at https:\/\/login.microsoftonline.com. If you skip this step, the login popup may fail to close when you call notifySuccess() or notifyFailure().<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h2>Step 2: Navigate to the Authorization Page<\/h2>\n<p>Next, from your login start page (e.g. in our example, \/tab-auth\/simple-start), navigate to the authorization page of the identity provider. You can return an HTTP 302 response from your server, or do this client-side by returning a page with JavaScript that calls window.location.assign(). Our example does this client-side, so that it can use the \u00e2\u20ac\u0153hinting\u00e2\u20ac\u009d information from the Microsoft Teams client SDK function microsoftTeams.getContext() to streamline the login flow:<\/p>\n<pre>microsoftTeams.getContext(function (context) {\n\u00a0\u00a0\u00a0 \/\/ Generate random state string and store it, so we can verify it in the callback\n\u00a0\u00a0\u00a0 let state = _guid(); \/\/ _guid() is a helper function in the sample\n\u00a0\u00a0\u00a0 localStorage.setItem(\"simple.state\", state);\n\u00a0\u00a0\u00a0 localStorage.removeItem(\"simple.error\");<\/pre>\n<pre>\u00a0\u00a0\u00a0 \/\/ Go to the Azure AD authorization endpoint\n\u00a0\u00a0\u00a0 let queryParams = {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 client_id: \"YOUR_APP_ID_HERE\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 response_type: \"id_token token\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 response_mode: \"fragment\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 resource: \"https:\/\/graph.microsoft.com\/User.Read openid\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 redirect_uri: window.location.origin + \"\/tab-auth\/simple-end\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nonce: _guid(),\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 state: state,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ The context object is populated by Teams; the upn attribute\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ is used as hinting information\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 login_hint: context.upn,\n\u00a0\u00a0\u00a0 };\n\u00a0\u00a0\u00a0 let authorizeEndpoint = \"https:\/\/login.microsoftonline.com\/common\/oauth2\/authorize?\" + toQueryString(queryParams);\n\u00a0\u00a0\u00a0 window.location.assign(authorizeEndpoint);\n});<\/pre>\n<p>&nbsp;<\/p>\n<p>Notes:<\/p>\n<ul>\n<li>As described earlier, this example uses the <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/active-directory\/develop\/active-directory-dev-understanding-oauth2-implicit-grant\">Azure AD OAuth2 Implicit Grant<\/a> flow to get an access token for Microsoft Graph and an id token for the user. By performing the authorization in the microsoftTeams.getContext() callback function, the username field of the login prompt can be pre-filled with the user principal name (UPN) from the tab context. This saves the user from typing and may even allow the authentication flow to complete without further user interaction if the user is already logged in.<\/li>\n<li>If you&#8217;re doing client-side redirection, call window.location.assign(). The microsoftTeams.navigateCrossDomain() function is not available in the context of the authentication popup. As a result, it is not necessary to include the the identity provider&#8217;s domain (e.g., for Azure AD, login.microsoftonline.com) in the validDomains[] list in the app&#8217;s manifest.json file.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h2>Step 3: User Sign-In and Authorization<\/h2>\n<p>The user signs in to the identity provider and authorizes your application. The identity provider navigates to your login callback URL (the one you specified in the <a href=\"https:\/\/apps.dev.microsoft.com\/\">Application Registration Portal<\/a>, e.g. https:\/\/<em>yourhost<\/em>\/tab-auth\/simple-end) should determine if authentication was successful and call either the microsoftTeams.authentication.notifySuccess() or microsoftTeams.authentication.notifyFailure() functions from the Microsoft Teams client SDK.<\/p>\n<h4>Note: notifyFailure() has the following predefined failure reasons:<\/h4>\n<ul>\n<li>CancelledByUser &#8211; the user closed the popup window before completing the authentication flow.<\/li>\n<li>FailedToOpenWindow &#8211; the popup window could not be opened. When running Microsoft Teams in a browser, this typically means that the window was blocked by a popup blocker.<\/li>\n<\/ul>\n<p>Below is the example code of the JavaScript code in the login callback URL&#8217;s page that illustrates how to call notifySuccess() and notifyFailure():<\/p>\n<pre>\/\/ Split the key-value pairs passed from Azure AD\n\/\/ getHashParameters is a helper function that parses the arguments sent\n\/\/ to the callback URL by Azure AD after the authorization call\nlet hashParams = getHashParameters(); \nif (hashParams[\"error\"]) {\n\u00a0\u00a0\u00a0 \/\/ Authentication\/authorization failed\n\u00a0\u00a0\u00a0 microsoftTeams.authentication.notifyFailure(hashParams[\"error\"]);\n} else if (hashParams[\"access_token\"]) {\n\u00a0\u00a0\u00a0 \/\/ Get the stored state parameter and compare with incoming state\n\u00a0\u00a0\u00a0 \/\/ This validates that the data is coming from Azure AD\n\u00a0\u00a0\u00a0 let expectedState = localStorage.getItem(\"simple.state\");\n\u00a0\u00a0\u00a0 if (expectedState !== hashParams[\"state\"]) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ State does not match, report error\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 microsoftTeams.authentication.notifyFailure(\"StateDoesNotMatch\");\n\u00a0\u00a0\u00a0 } else {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Success: return token information to the tab\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 microsoftTeams.authentication.notifySuccess({\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 idToken: hashParams[\"id_token\"],\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 accessToken: hashParams[\"access_token\"],\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 tokenType: hashParams[\"token_type\"],\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 expiresIn: hashParams[\"expires_in\"]\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 })\n\u00a0\u00a0\u00a0 }\n} else {\n\u00a0\u00a0\u00a0 \/\/ Unexpected condition: hash does not contain error or access_token parameter\n\u00a0\u00a0\u00a0 microsoftTeams.authentication.notifyFailure(\"UnexpectedFailure\");\n}<code>\u00a0<\/code><\/pre>\n<p>The JavaScript above on the login callback URL&#8217;s page parses the key-value pairs received from Azure AD in window.location.hash using the getHashParameters() helper function. If it finds an access_token, and the \u00e2\u20ac\u0153state\u00e2\u20ac\u009d value is the same as the one provided at the start of the auth flow, it returns the access token to the tab by calling notifySuccess(); otherwise it reports an error with notifyFailure(). The tab receives the result, or the failure reason, in the callback functions it provided in the call to authenticate().<\/p>\n<h2>&#8220;Silent&#8221;\u009d Authentication<\/h2>\n<p>If you want to keep your code completely client-side, you can use the Azure Active Directory <a href=\"https:\/\/github.com\/AzureAD\/azure-activedirectory-library-for-js\">Authentication Library for Javascript<\/a> to attempt to acquire an Azure AD access token silently (that is, without the user ever seeing a popup dialog). Even though the ADAL.js library is optimized for working with AngularJS applications, it&#8217;s certainly usable from pure JavaScript single-page applications.<\/p>\n<p>The diagram below illustrates how \u00e2\u20ac\u0153silent\u00e2\u20ac\u009d authentication works:<\/p>\n<p>The ADAL.js library creates a hidden &lt;iframe&gt; for the implicit grant flow, but it specifies prompt=none so that Azure AD never shows the login page. If user interaction is required, whether because the user needs to log in or grant access to the application, Azure AD will immediately return an error that ADAL.js reports to your app. At this point your app can show a login button.<\/p>\n<p>1. Include the ADAL.js library in your tab pages and configure ADAL with your client ID and redirect URL:<\/p>\n<pre>&lt;script src=\"https:\/\/secure.aadcdn.microsoftonline-p.com\/lib\/1.0.15\/js\/adal.min.js\" integrity=\"sha384-lIk8T3uMxKqXQVVfFbiw0K\/Nq+kt1P3NtGt\/pNexiDby2rKU6xnDY8p16gIwKqgI\" crossorigin=\"anonymous\"&gt;&lt;\/script&gt;\n&lt;script type=\"text\/javascript\"&gt;\n\u00a0\u00a0\u00a0 \/\/ ADAL.js configuration\n\u00a0\u00a0\u00a0 let config = {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 clientId: \"YOUR_APP_ID_HERE\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ redirectUri must be in the list of redirect URLs for the AAD app\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 redirectUri: window.location.origin + \"\/tab-auth\/silent-end\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cacheLocation: \"localStorage\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 navigateToLoginRequestUrl: false,\n\u00a0\u00a0\u00a0 };\n&lt;\/script&gt;<\/pre>\n<p>2. In the tab&#8217;s content page:<\/p>\n<p style=\"padding-left: 30px\">a. Call microsoftTeams.getContext() to get the current user&#8217;s UPN. Note that we won&#8217;t be relying on the value reported in the context, but rather, use it as a login_hint in the call to Azure AD.<\/p>\n<pre>\/\/ Set up extra query parameters for ADAL\n\/\/ - openid and profile scope adds profile information to the id_token\n\/\/ - login_hint provides the expected user name\nif (upn) {\n\u00a0\u00a0\u00a0 config.extraQueryParameters = \"scope=openid+profile&amp;login_hint=\" + encodeURIComponent(upn);\n} else {\n\u00a0\u00a0\u00a0 config.extraQueryParameters = \"scope=openid+profile\";\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \n}<\/pre>\n<p style=\"padding-left: 30px\">b. If ADAL has an unexpired token cached for the user, use that.<\/p>\n<p style=\"padding-left: 30px\">c. Otherwise, attempt to get a token silently. Call _renewIdToken(callback) for an id token, or _renewToken(resource, callback) for an access token. ADAL.js will call your callback function with the requested token, or an error if it fails.<\/p>\n<p style=\"padding-left: 30px\">d. If you get an error in the callback function, show a login button and fall back to an explicit login.<\/p>\n<pre>let authContext = new AuthenticationContext(config); \/\/ from the ADAL.js library<\/pre>\n<pre>\/\/ See if there's a cached user and it matches the expected user\nlet user = authContext.getCachedUser();\nif (user) {\n\u00a0\u00a0\u00a0 if (user.userName !== upn) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ User doesn't match, clear the cache\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 authContext.clearCache();\n\u00a0\u00a0\u00a0 }\n}<\/pre>\n<pre>\/\/ Get the id token (which is the access token for resource = clientId)\nlet token = authContext.getCachedToken(config.clientId);\nif (token) {\n\u00a0\u00a0\u00a0 showProfileInformation(token);\n} else {\n\u00a0\u00a0\u00a0 \/\/ No token, or token is expired\n\u00a0\u00a0\u00a0 authContext._renewIdToken(function (err, idToken) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (err) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 console.log(\"Renewal failed: \" + err);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Failed to get the token silently; show the login button\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 showLoginButton();\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ You could attempt to launch the login popup here, but in browsers this could be blocked by\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ a popup blocker, in which case the login attempt will fail with the reason FailedToOpenWindow.\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 } else {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 showProfileInformation(idToken);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0\u00a0\u00a0 });\n}<\/pre>\n<p>&nbsp;<\/p>\n<ol start=\"3\">\n<li>Let ADAL.js take care of parsing the result from Azure AD by calling AuthenticationContext.handleWindowCallback(hash) in the login callback page. Next, check that we have a valid user, and call microsoftTeams.authentication.notifySuccess() or microsoftTeams.authentication.notifyFailure() to report status back to your main tab content page.<\/li>\n<\/ol>\n<pre><code>\u00a0if (authContext.isCallback(window.location.hash)) {<\/code>\n<code>\u00a0 authContext.handleWindowCallback(window.location.hash);<\/code>\n<code>\u00a0 if (authContext.getCachedUser()) {<\/code>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0 microsoftTeams.authentication.notifySuccess();<\/code>\n<code>\u00a0 } else {<\/code>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0 microsoftTeams.authentication.notifyFailure(authContext.getLoginError());<\/code>\n<code>\u00a0 }<\/code>\n<code>}<\/code><\/pre>\n<h2>Using an Existing Login Flow<\/h2>\n<p>The above examples illustrate how to perform authentication and authorization against Microsoft Active Directory. What if have an existing web application that requires authentication, and want to allow users to pin pages from that site as a tab?<\/p>\n<p>For example, suppose our site is at <a href=\"https:\/\/example.com\">https:\/\/example.com<\/a>, and:<\/p>\n<ul>\n<li>We want to pin the content at <a href=\"https:\/\/example.com\/page\/1\">https:\/\/example.com\/page\/1<\/a><\/li>\n<li>The app&#8217;s login page is at <a href=\"https:\/\/example.com\/login\">https:\/\/example.com\/login<\/a>, and after login, it goes to the path specified by the &#8220;next&#8221;\u009d query parameter.<\/li>\n<\/ul>\n<p>The diagram below illustrates this flow:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/05\/Auth_Image.jpg\" alt=\"path specified by the \" width=\"740\" height=\"379\" \/><\/p>\n<p>There&#8217;s no source code for this flow in the <a href=\"https:\/\/github.com\/OfficeDev\/microsoft-teams-sample-complete-node\">Node.js<\/a> and <a href=\"https:\/\/github.com\/OfficeDev\/microsoft-teams-sample-complete-csharp\">C#<\/a> examples, but here&#8217;s an overview of what to do:<\/p>\n<p>1. Create a post-login page at <a href=\"https:\/\/example.com\/tab-auth-done\">https:\/\/example.com\/tab-auth-done<\/a>. On this page, check if the user&#8217;s login succeeded, then call notifySuccess() or notifyFailure() accordingly.<\/p>\n<p>2. Create a tab content page at <a href=\"https:\/\/example.com\/tab-content\">https:\/\/example.com\/tab-content<\/a>. This page takes the URL of the actual content page as a &#8220;contentUrl&#8221;\u009d parameter.<\/p>\n<p style=\"padding-left: 30px\">a. On load, check if the user is logged in to the site.<\/p>\n<p style=\"padding-left: 30px\">b. If the user is logged in, navigate to the actual content by calling navigateCrossDomain().We recommend calling navigateCrossDomain() even if the target is actually on the same domain because Microsoft Teams validates that that domain of the target URL is in the valid domains list. This keeps your tab from navigating to an untrusted location and losing its connection to Microsoft Teams.<\/p>\n<p style=\"padding-left: 30px\">c. Otherwise, show a login button. In the button&#8217;s click handler, call authenticate() with the URL: <a href=\"https:\/\/example.com\/login?next=https:\/\/example.com\/tab-auth-done\">https:\/\/example.com\/login?next=https:\/\/example.com\/tab-auth-done<\/a>. If it succeeds, navigate to the actual content as above. If it fails, you might opt to show an error message, but remember to keep showing the login button so that the user can try again.<\/p>\n<p>3. Configure the tab settings as follows:<\/p>\n<p style=\"padding-left: 30px\">a. Set contentUrl to <a href=\"https:\/\/example.com\/tab-content?contentUrl=https\/\/example.com\/page\/1\">https:\/\/example.com\/tab-content?<\/a>contentUrl=https:\/\/example.com\/page\/1, so the tab goes to the landing page first.<\/p>\n<p style=\"padding-left: 30px\">b. Set webUrl to <a href=\"https:\/\/example.com\/tab-content?contentUrl=https\/\/example.com\/page\/1\">https\/\/example.com\/page\/1<\/a>, so we go directly to the web content when the user chooses to view your tab in a browser.<\/p>\n<h2>Conclusion<\/h2>\n<p>In this article, we&#8217;ve explained how to perform authentication and authorization against Azure Active Directory, how to do single sign-on, and how to retrieve information using Microsoft Graph. This article, along with the Node.js\/Typescript and C# samples, should illustrate these sophisticated and powerful techniques.<\/p>\n<p>For reference, be sure to refer to these samples:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/OfficeDev\/microsoft-teams-sample-complete-node\">js\/Typescript<\/a>.<\/li>\n<li><a href=\"https:\/\/github.com\/OfficeDev\/microsoft-teams-sample-complete-csharp\">C#<\/a><\/li>\n<\/ul>\n<p>Happy authentication and authorization!<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Some of the most common questions we receive from Microsoft Teams developers concern authentication to Azure Active Directory (Azure AD), single sign-on (SSO) to Azure AD, and how to access Microsoft Graph APIs from within a Microsoft Teams app. Here, we&#8217;ll explain in detail how to do these things, going above and beyond authentication basics.<\/p>\n","protected":false},"author":69074,"featured_media":187,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[128],"tags":[],"class_list":["post-186","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-microsoft-teams"],"acf":[],"blog_post_summary":"<p>Some of the most common questions we receive from Microsoft Teams developers concern authentication to Azure Active Directory (Azure AD), single sign-on (SSO) to Azure AD, and how to access Microsoft Graph APIs from within a Microsoft Teams app. Here, we&#8217;ll explain in detail how to do these things, going above and beyond authentication basics.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/186","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/users\/69074"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/comments?post=186"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/186\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media\/187"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media?parent=186"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/categories?post=186"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/tags?post=186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}