Using Microsoft Graph API without user (Application Identity) on Teams Custom App - microsoft-teams

I've been developing a Teams Custom App with the TeamsFx SDK.
I want to use the Microsoft Graph API using an Application identity.
So, I referred to the Microsoft official documentation, however I wasn't able to achieve what I wanted to do.
 - Referred document: https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/teamsfx-sdk.
Under "Create MicrosoftGraphClient service", "Invoke Graph API without user (Application Identity)" section.
I tried the following:
I created a new Teams app from "SSO-enabled tab" sample with Teams Toolkit on Visual Studio Code.
I edited a Graph.jsx as below to get a user info.
import { createMicrosoftGraphClient, IdentityType, TeamsFx } from "#microsoft/teamsfx";
useEffect(() => {
const getProfile = async () => {
const teamsfx = new TeamsFx(IdentityType.App);
const graphClient = createMicrosoftGraphClient(teamsfx);
const profile = await graphClient.api("/users/username#domain.onmicrosoft.com").get();
return profile;
};
const profile = getProfile();
}, []);
I debugged the project by hitting the F5 key in Visual Studio Code.
Although I tried what the document said, the console log said "Application identity is not supported in TeamsFx".
How should do I edit my projec to use Microsoft Graph API without a user identity (i.e. using Application Identity)?

Application Identity is not supported in browser (Tab page), so you need a server environment to use it.
You could add an Azure Function and use the Application Identity in it to achieve desired effect. Here's the steps in Teams Toolkit v4.0.1:
Create new project from "SSO-enabled tab" template.
Choose "Azure Functions" in "Add features" with default name.
Modify the code in "api/getUserProfile/index.ts".
teamsfx = new TeamsFx(IdentityType.App);
...
const graphClient: Client = createMicrosoftGraphClient(teamsfx, [".default"]);
const profile: any = await graphClient.api("/users/username#domain.onmicrosoft.com").get();
res.body.graphClientMessage = profile;
Configure "User.Read.All" permission and grant admin consent on AAD portal.
Run "F5" and click "Call Azure Function" on tab page, the Azure Function will be invoked and get Graph data using Application Identity. You should see the user information below.

What you're trying to do is insecure, because you have code running as Application level security, but running on the client side. Code running with that kind of privilege should only be running on the server side, or in this case behind a protected API (e.g. one that it validating the user's security using SSO to ensure the user is valid, and then executing on the server).
This video gives a bit of an idea of how it should be working: https://www.youtube.com/watch?v=kruUnaZgQaY
In the video, they're actually doing a token -exchange- (exchanging from the pure client side SSO token, and getting an "on behalf of" token in the backend, and making the call then). You can skip that part and just use the application token, but you should, as I mentioned, have this occurring in the server, not on the client.

Related

Azure Bot Single Sign On between Single Page Application using authorization code flow

As per the Microsoft documentation, it is expecting us to use implicit flow for Single Sign On between Bot and Single Page Application. At the same time, Microsoft recommends us to move to auth code flow. Our client's Azure AD team is not allowing Implicit flow anymore. Can auth code flow work for SSO between SPA and Azure Bot?
Bot framework using implicit flow
https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/authentication/auth-aad-sso-bots
Implicit flow
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow
Create a bot application.
Go to ADD – App registration (https://go.microsoft.com/fwlink/?linkid=2083908)
Register one application
Go to Expose an API
Get the Application ID (Client ID)
Go to Expose an API
Paste the App ID and click on Save and Continue
Go to Client application and Click on Add a Client application
Go to Authentication and Click on Add a Platform
Click on Single page application.
Give the application redirection URI details.
Click on Configuration in Bot settings
Under configuration. Click on Add OAuth connection. Fill the required details.

Interactive Logon Required for Azure Resources NodeJS App

I am looking to pull a list of Azure resources such as VMs, AppServices, etc and possibly interact (create, delete, scale, etc.) via the Azure SDK for NodeJS. The examples seem to demonstrate/push the use of an interactive login.
The reason I don't want to use the interactive logon is so I can schedule these tasks instead of requiring interaction.
Example, I looked at the authentication module and it is focused on interactive logon as well. Is there another means to authenticate instead of interactive as the previous SDK seemed to allow to authentication via secrets and subscription ID:
//Environment Setup
_validateEnvironmentVariables();
var clientId = process.env['CLIENT_ID'];
var domain = process.env['DOMAIN'];
var secret = process.env['APPLICATION_SECRET'];
var subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
var credentials = new msRestAzure.ApplicationTokenCredentials(clientId, domain, secret, { 'tokenCache': tokenCache });
After assistance with the SDK team, there are options for auth for node, but better to get these from the node site and use the #azure ones as those are the most up to date. Ex: https://www.npmjs.com/package/#azure/ms-rest-nodeauth

How to integrate BotFramework with Hangouts Chat

I'm trying to integrate my bot too the Hangouts Chat API. I migrated from C# to node.js in order to be able to take advantage of the HangoutsAdapter: https://botkit.ai/docs/v4/platforms/hangouts.html
The problem is that the HangoutsAdapter expects a google token and when I go to the Hangouts Chat API configuration tab, I select Bot URL under Connection Settings section, but the only thing I get is a field to enter my bot's url endpoint. Nothing about the Verification Token I'm supposed to pass to the Hangouts Adapter.
Is there any other way to validate the connection to that API with HangoutsAdapter? Should I use something else rather than HangoutsAdapter? Or maybe I should use it in a different way?
Technically, this is an alternative solution (I think). In order to create Google Hangouts credentials a GSuite account is required, which I don't have. The Interface HangoutsAdapterOptions docs state the "Shared secret token [is] used" for validation and is found under the Configuration tab. As the fields are locked down, I can't test this.
However, the alternative is to use the built-in OAuth feature of the Bot Framework. I use this in my bot without a hitch. I did navigate to the Google Hangouts web site and it recognized me immediately. By using the OAuth login, a token is returned which you can use to pass into the Hangouts adapter.
I should add that the below implementation produces a sign-in magic code which some people don't like (it doesn't bother me). There may be a SSO option, but I haven't researched that.
First, you need to setup Google credentials which will provide you with a "Client id" and "Client secret". General instructions can be found here. Credentials can be created here. You can ignore any coding references as they aren't necessary (thanks to the BF OAuth feature).
From the Credentials Page, click on "Credentials" in the left menu. There are two areas you need to configure, once there: "OAuth consent screen" and "Credentials".
For OAuth consent screen, provide an app name. This doesn't need to match the associated requesting app and is just for reference when visiting the Credentials Page. Enter a support email, the scopes you will be using (email, profile, and openid). There may be other required scopes, but this worked for me when I visited the Hangouts web site. Lastly, enter in the Authorized Domains. The botframework.com domain is required. Others, if any, you will have to experiment with. Save and return to the Credentials Page.
Click the "Create Credentials" button and complete the form. Give your credentials a name (again, not referenced anywhere else for this project), enter any authorized origins, and enter https://token.botframework.com/.auth/web/redirect as the authorized redirect URI. Save the settings, copy the "Client id" and "Client secret" somewhere, and navigate to Azure and into your bot's settings page.
Here, you need to create your bot's OAuth connection. This is done in the Settings blade. At the bottom of the blade is the "OAuth Connection Settings" section. Click the "Add Setting" button to get started.
Once in, give your connection a name. This name will be referenced by your bot in the next step, so save the name somewhere. Then, select Google from the list of Service Providers and paste the "Client id" and "Client secret", that you saved earlier, into the respective fields. In "Scopes", you will want to enter the same scope values you selected in your Google credentials app ("email profile openid"). Be sure they are space-separated when you enter them.
Lastly, you will want to model your OAuth login off of sample 18.bot-authentication from the Botbuilder-Samples repo. This builds in the functionality you need for enabling a user to log in via your bot.
Add the connection name you assigned to your Google connection setting for your bot into a .env file as a variable, like this: connectionName=<CONNECTION_NAME>.
When setting up the OAuth prompt, you will pass this variable in:
this.addDialog(new OAuthPrompt(OAUTH_PROMPT, {
connectionName: process.env.connectionName,
text: 'Please Sign In',
title: 'Sign In',
timeout: 300000
}));
At this point, your bot and login process should be good to go. Assuming the flow is setup correctly, a user should be able to login via the OAuth prompt, by use of a magic code. A token is returned back to the bot which will be accessible via the context on the next step. The token can then be saved to state and passed to the adapter for use. In the below bit, I'm using a simple waterfall with an oauthPrompt step and a loginResults step. The token is captured in the second step where I console log it.
async oauthPrompt(step) {
return await step.prompt(OAUTH_PROMPT, {
prompt: {
inputHint: 'ExpectingInput'
}
});
}
async loginResults(step) {
let tokenResponse = step.result;
console.log('TOKEN: ', tokenResponse);
if (tokenResponse != null) {
await step.context.sendActivity('You are now logged in.');
return await step.prompt(CONFIRM_PROMPT, 'Do you want to view your token?', ['yes', 'no']);
}
// Something went wrong, inform the user they were not logged in
await step.context.sendActivity('Login was not successful please try again');
return await step.endDialog();
}
Hope of help!
I created an issue on https://github.com/howdyai/botkit/issues/1722
Basically hangouts adapter expects a token in order to compare it to the token gotten from the hangouts chat api. But given that the token is not provided anymore by google, the authentication mechanism needs to change

Accessing google apis on behalf of google home user

I'm trying to develop a simple ProofOfConcept to interract with Google APIs from a Google Home using the authorization of the user through Dialogflow Fullfilment.
For example, we want to be able to create/read/update/delete contacts of users.
It's relatively easy to implement the Google SignIn but I don't know how to ask for additionnal scopes
Also, I never implemented an OAuth server before and I'm still not sure if I need too or if I could reuse en existing one.
I looked at many posts here on SO most of them was answered by #Prisoner who also provided a detailed flow in Google Home Authorization Code and Authentication with Google Account
In his answer, he mentions that we can "redirect the user to a web login page" but I still don't understand how to do that from the fullfilment
Here's the fullfilment code I used to use the Google SignIn:
//requirements
const { dialogflow, SignIn} = require('actions-on-google');
const functions = require('firebase-functions');
//initialisation
const app = dialogflow(
{
debug: true,
// REPLACE THE PLACEHOLDER WITH THE CLIENT_ID OF YOUR ACTIONS PROJECT
clientId: '1111111111111-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com'
});
//Welcome Intent
app.intent('Default Welcome Intent', (conv) => {
conv.ask(`Welcome to the demo agent, say "Connect-me" to proceed`);
});
//Default Intent
app.intent('Default Fallback Intent', (conv) => {
conv.ask(`Sorry, can you repeat please?`);
});
//Intent starting the Google SignIn
// Create an intent with the name "Start Signin"
app.intent('Start Signin', (conv) => {
//SignIn Helper (https://developers.google.com/actions/assistant/helpers#account_sign-in)
conv.ask(new SignIn(`Need to identify you`));
});
//Intent called when the user complete the authentication
// Create an intent with the name "Get Signin" and assign it the event "actions_intent_SIGN_IN"
app.intent('Get Signin', (conv, params, signin) => {
if (signin.status === 'OK') {
const payload = conv.user.profile.payload;
conv.ask(`Hello ${payload.name}. You can now access your contacts informations... but not really `);
} else {
conv.ask(`Sorry but you should authentify yourself `);
}
});
//Example of intent that I would like to make it works
app.intent('How many contacts', (conv) => {
/*
TODO: How to ask for additional scopes ("https://www.google.com/m8/feeds/" : read/write access to Contacts and Contact Groups)
NOTE: Actually, I'm more interrested on how to get the additional scopes.
I could probably do the querying contacts part by myself since it's quite documented (https://developers.google.com/contacts/v3/)
*/
conv.ask(new SignIn(`You have ${nbContacts} contacts defined in your Google Contact`));
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
You have a few related questions bundled here.
How do I ask for additional scopes with Google Sign In?
Unfortunately you can't.
This is for the user's security. Since it isn't always obvious when you're granting additional permissions via voice, and asking for too many scopes can be overwhelming with voice, they don't allow this right now.
Ok. So how do I get those additional scopes?
You referenced another post that goes through the method I propose to do that. It involves signing in and granting the scopes through a web page in the same Google Cloud Project. Under Google's cross-client identity system, these would then apply when the user connects through the Assistant as well.
So how do I direct them to a web page?
I typically either use a card with a link button for the website or a link out suggestion to the site. Both of these, however, depend on being on a visual interface that supports a web browser. So you might wish to check for this using surface capabilities.
If you are using your own OAuth server, using the combination "OAuth and Google Sign-In" method, the Assistant will take care of these sorts of things for you.
Do I need to write my own OAuth server?
No. You can use something like Auth0 to handle the OAuth part for you.

Azure Active Directory Consent Framework not kicking in for .NET client application

I have a Web API running in an Azure Web App. It is used from a .NET WinForms client application. In the same Azure Web App, there is also an ASP.NET MVC site.
Users authenticate to the site and the .NET client using Azure AD credentials. This should be multi-tenant. The MVC app works fine multi-tenant, but I have trouble getting the client to run multi-tenant.
My understanding from https://learn.microsoft.com/en-us/azure/active-directory/active-directory-integrating-applications is that the Consent Framework should kick in automatically if OAuth 2.0 is used. I am using code (below) that is very close to the sample at https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapi-multitenant-windows-store/.
While I can successfully log in with a user in the tenant where the native app is defined and use the app, I cannot use the app with a user from another tenant. There is no consent asked and I get an AdalException:
AADSTS50001: The application named https://<myurl> was not found in the tenant named <sometenant>.onmicrosoft.com. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
Trace ID: 3<snip>2
Correlation ID: 2<snip>a
Timestamp: 2017-01-05 01:01:10Z
I have added the ID of the native client app to the list of knownClientApplications in the web app's manifest. I am authenticating against the "common" tenant (https://login.microsoftonline.com/common). The third-party tenant user account signing in is a Global Admin in that tenant.
So, I am clearly overlooking something to enable the Consent Framework but I can't find what it is based on the sample...
Relevant native client code below
Uri RedirectUri = new Uri(sRedirectUri);
// AadInstance is the common tenant
AuthenticationContext authContext = new AuthenticationContext(AadInstance);
try
{
PlatformParameters pp = new PlatformParameters(PromptBehavior.Auto);
// Authenticate to Azure AD
Program.WebApiAuthenticationResult = await authContext.AcquireTokenAsync(WebAppIdUri, ClientID, RedirectUri, pp);
return true;
}
catch (AdalException ex)
{
MessageBox.Show(ex.Message, "Log In Not Successful");
}
Thanks for any insight!
I am trying to reproduce this issue however failed. The scenario that native client app consume the web API which both protect by Azure works well when I login the native app with the multiple tenant account. I tested the code sample from here.
And based on the error message, it seems that the resource is not correct, please ensure it is same as the APP ID URI for the web API app you register on Azure. I could get the same error message when I specify an incorrect resource.

Resources