microsoft / teams-ai

SDK focused on building AI based applications and extensions for Microsoft Teams and other Bot Framework channels
MIT License
395 stars 170 forks source link

[Bug]: AxiosError: Request failed with status code 401 #1203

Closed donatas-xyz closed 6 months ago

donatas-xyz commented 7 months ago

Language

Javascript/Typescript

Version

latest

Description

I've been trying to implement SSO on bot existing Teams AI Bot app and a new one as well following this example.

In both cases I'm getting "AxiosError: Request failed with status code 401" as soon as I make a request to AI model.

I've tried a myriad of things for days and went through endless examples of similar implementations with nothing else left to check.

The bot is able to successfully:

However, as soon as I make request that would involve the AI model itself (i.e. "Who are you?"), it returns Axios 401 error.

What is not making it easier is that I'm behind a corporate firewall and using a lot of solutions for debugging is simply out of question for me. I can only reliably use Azure's log stream (which only shows the same error anyway) and the Axios output itself, but it doesn't output anything else, so I don't know what exactly is it not happy about.

I'm not an expert in front-end development, so I would appreciate any other tips on how to work it out what the actual issue here is?

Reproduction Steps

1. Create a new AI Chatbot app via Teams Toolkit 
2. Change Azure AI credentials to match your own 
3. Provision and deploy the app to Azure - this will create MS Entra app entry and populate missing credentials in your Teams app 
4. Test this very minimal app inside Azure Web Chat or directly in Teams or locally via Bot Framework Emulator - it all should work. 
5. Now add all necessary AAD config and code as shown at https://github.com/microsoft/teams-ai/tree/main/js/samples/06.auth.teamsSSO.bot 
6. Make sure secrets match etc and don't forget to copy public/*.html files to your /lib as well
7. Deploy the app again - certain MS Entra app entries will get updated according to your aad.manifest.json 
8. Run the app again - it will request your consent as expected. Confirm it. 
9. Try requesting something what's worked before (i.e. "Who are you?") 
10. The AxiosError 401 should come back
lilyydu commented 7 months ago

Hi @BlueWhaleSEO, some questions to clarify further:

  1. Which sample bot are you running into this issue?
  2. Are you using AzureOpenAI or OpenAI?
  3. Prior to step 5, are you making any requests to the AI model? If so, are these successful?

Could you also attach snippets of where/when you are making these requests, and how you set up your connection to the AI model?

401's generally mean the setup to connect to the API, or the call to the API, is formatted incorrectly (e.g., invalid or missing fields/credentials). I'm guessing this is likely the case for you

In the meantime, I would also suggest looking into our ChefBot sample to see how we connect to AzureOpenAI/OpenAI

donatas-xyz commented 7 months ago

Hi @lilyydu. Thank you for coming back to me.

  1. It's the example at https://github.com/microsoft/teams-ai/tree/main/js/samples/06.auth.teamsSSO.bot
  2. I'm using AzureOpenAI.
  3. Yes, prior step 5 (before AAD config is added) everything works fine. Therefore ChefBot example is irrelevant here, as it's not using SSO and the bot works up to adding SSO support anyway.

This is just the simplest skeleton bot created with the Teams Toolkit. I have much more complex bot, which is suffering from the same issue - works fine until SSO is added.

app.ts

import { MemoryStorage } from "botbuilder";
import * as path from "path";
import { Application, ActionPlanner, OpenAIModel, PromptManager } from "@microsoft/teams-ai";
import config from "./config";

// Create AI components
const model = new OpenAIModel({
  // Use Azure OpenAI
  azureApiKey: config.azureOpenAIKey!,
  azureDefaultDeployment: "gpt3_5-xxx-custom-model",
  azureEndpoint: config.azureOpenAIEndpoint!,
  useSystemMessages: true,
  logRequests: true,
});
const prompts = new PromptManager({
  promptsFolder: path.join(__dirname, "../src/prompts"),
});
const planner = new ActionPlanner({
  model,
  prompts,
  defaultPrompt: "chat",
});

// Define storage and application
const storage = new MemoryStorage();
const app = new Application({
  storage,
  ai: {
    planner,
  },
  authentication: {settings: {
    graph: {
      scopes: ['User.Read'],
      msalConfig: {
          auth: {
              clientId: process.env.AAD_APP_CLIENT_ID!,
              clientSecret: process.env.AAD_APP_CLIENT_SECRET!,
              authority: `${process.env.AAD_APP_OAUTH_AUTHORITY_HOST}/${process.env.AAD_APP_TENANT_ID}`
          }
      }, 
      signInLink: `https://${process.env.BOT_DOMAIN}/auth-start.html`,
      endOnInvalidMessage: true
  }, 
}}
});

export default app;

index.ts

// Import required packages
import * as path from 'path';
import * as restify from "restify";

// Import required bot services.
// See https://aka.ms/bot-services to learn more about the different parts of a bot.
import {
  CloudAdapter,
  ConfigurationBotFrameworkAuthentication,
  ConfigurationServiceClientCredentialFactory,
} from "botbuilder";

// This bot's main dialog.
import app from "./app";
import config from "./config";

const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(
  {},
  new ConfigurationServiceClientCredentialFactory({
    MicrosoftAppId: config.botId,
    MicrosoftAppPassword: process.env.BOT_PASSWORD,
    MicrosoftAppType: "MultiTenant",
  })
);

// Create adapter.
// See https://aka.ms/about-bot-adapter to learn more about how bots work.
const adapter = new CloudAdapter(botFrameworkAuthentication);

// Catch-all for errors.
const onTurnErrorHandler = async (context, error) => {
  // This check writes out errors to console log .vs. app insights.
  // NOTE: In production environment, you should consider logging this to Azure
  //       application insights.
  console.error(`\n [onTurnError] unhandled error: ${error}`);

  // Send a trace activity, which will be displayed in Bot Framework Emulator
  await context.sendTraceActivity(
    "OnTurnError Trace",
    `${error}`,
    "https://www.botframework.com/schemas/error",
    "TurnError"
  );

  // Send a message to the user
  await context.sendActivity(`The bot encountered an error or bug: ${error}`);
  await context.sendActivity(`To continue to run this bot, please fix the bot source code.`);
};

// Set the onTurnError for the singleton CloudAdapter.
adapter.onTurnError = onTurnErrorHandler;

// Create HTTP server.
const server = restify.createServer();
server.use(restify.plugins.bodyParser());

server.listen(process.env.port || process.env.PORT || 3978, () => {
  console.log(`\nBot Started, ${server.name} listening to ${server.url}`);
});

// Listen for incoming server requests.
server.post("/api/messages", async (req, res) => {
  // Route received a request to adapter for processing
  await adapter.process(req, res as any, async (context) => {
    // Dispatch to application for routing
    await app.run(context);
  });
});

server.get(
  '/auth-:name(start|end).html',
  restify.plugins.serveStatic({
      directory: path.join(__dirname, 'public')
  })
);

teamsapp.yml

provision:
  - uses: aadApp/create # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty
    with:
      name: SsoAuthBot-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here.
      generateClientSecret: true # If the value is false, the action will not generate client secret for you
      signInAudience: "AzureADMyOrg" # Authenticate users with a Microsoft work or school account in your organization's Azure AD tenant (for example, single tenant).
    writeToEnvironmentFile:
      # Write the information of created resources into environment file for the specified environment variable(s).
      clientId: AAD_APP_CLIENT_ID
      clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file
      objectId: AAD_APP_OBJECT_ID
      tenantId: AAD_APP_TENANT_ID
      authority: AAD_APP_OAUTH_AUTHORITY
      authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST

  - uses: aadApp/update # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update.
    with:
      manifestPath: ./aad.manifest.json # Relative path to teamsfx folder. Environment variables in manifest will be replaced before apply to AAD app
      outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json

aad.manifest.json

{
    "id": "${{AAD_APP_OBJECT_ID}}",
    "appId": "${{AAD_APP_CLIENT_ID}}",
    "name": "SsoAuthBot-aad",
    "accessTokenAcceptedVersion": 2,
    "signInAudience": "AzureADMultipleOrgs",
    "optionalClaims": {
        "idToken": [],
        "accessToken": [
            {
                "name": "idtyp",
                "source": null,
                "essential": false,
                "additionalProperties": []
            }
        ],
        "saml2Token": []
    },
    "requiredResourceAccess": [
        {
            "resourceAppId": "Microsoft Graph",
            "resourceAccess": [
                {
                    "id": "User.Read",
                    "type": "Scope"
                }
            ]
        }
    ],
    "oauth2Permissions": [
        {
            "adminConsentDescription": "Allows Teams to call the app's web APIs as the current user.",
            "adminConsentDisplayName": "Teams can access app's web APIs",
            "id": "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}",
            "isEnabled": true,
            "type": "User",
            "userConsentDescription": "Enable Teams to call this app's web APIs with the same rights that you have",
            "userConsentDisplayName": "Teams can access app's web APIs and make requests on your behalf",
            "value": "access_as_user"
        }
    ],
    "preAuthorizedApplications": [
        {
            "appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264",
            "permissionIds": [
                "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
            ]
        },
        {
            "appId": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346",
            "permissionIds": [
                "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
            ]
        },
        {
            "appId": "d3590ed6-52b3-4102-aeff-aad2292ab01c",
            "permissionIds": [
                "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
            ]
        },
        {
            "appId": "00000002-0000-0ff1-ce00-000000000000",
            "permissionIds": [
                "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
            ]
        },
        {
            "appId": "bc59ab01-8403-45c6-8796-ac3ef710b3e3",
            "permissionIds": [
                "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
            ]
        },
        {
            "appId": "0ec893e0-5785-4de6-99da-4ed124e5296c",
            "permissionIds": [
                "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
            ]
        },
        {
            "appId": "4765445b-32c6-49b0-83e6-1d93765276ca",
            "permissionIds": [
                "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
            ]
        },
        {
            "appId": "4345a7b9-9a63-4910-a426-35363201d503",
            "permissionIds": [
                "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
            ]
        }
    ],
    "identifierUris":[
        "api://botid-${{BOT_ID}}"
    ],
    "replyUrlsWithType":[
        {
          "url": "https://${{BOT_DOMAIN}}/auth-end.html",
          "type": "Web"
        }
    ]
}

azure.parameters.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "resourceBaseName": {
      "value": "bot${{RESOURCE_SUFFIX}}"
    },
    "botAadAppClientId": {
      "value": "${{BOT_ID}}"
    },
    "botAadAppClientSecret": {
      "value": "${{SECRET_BOT_PASSWORD}}"
    },
    "openAIKey": {
      "value": "${{SECRET_OPENAI_API_KEY}}"
    },
    "azureOpenAIKey": {
      "value": "${{SECRET_AZURE_OPENAI_API_KEY}}"
    },
    "azureOpenAIEndpoint": {
      "value": "${{SECRET_AZURE_OPENAI_ENDPOINT}}"
    },
    "webAppSKU": {
      "value": "B1"
    },
    "botDisplayName": {
      "value": "MS_Teams_Bot-PoC"
    },
    "aadAppClientId": {
      "value": "${{AAD_APP_CLIENT_ID}}"
    },
    "aadAppClientSecret": {
      "value": "${{SECRET_AAD_APP_CLIENT_SECRET}}"
    },
    "aadAppTenantId": {
      "value": "${{AAD_APP_TENANT_ID}}"
    },
    "aadAppOauthAuthorityHost": {
      "value": "${{AAD_APP_OAUTH_AUTHORITY_HOST}}"
    }
  }
}

azure.bicep

@maxLength(20)
@minLength(4)
@description('Used to generate names for all resources in this file')
param resourceBaseName string

@description('Required when create Azure Bot service')
param botAadAppClientId string

@secure()
@description('Required by Bot Framework package in your bot project')
param botAadAppClientSecret string

@secure()
param openAIKey string

@secure()
param azureOpenAIKey string

@secure()
param azureOpenAIEndpoint string

param webAppSKU string

@maxLength(42)
param botDisplayName string

param serverfarmsName string = resourceBaseName
param webAppName string = resourceBaseName
param location string = resourceGroup().location
param aadAppClientId string
param aadAppTenantId string
param aadAppOauthAuthorityHost string
@secure()
param aadAppClientSecret string

// Compute resources for your Web App
resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = {
  kind: 'app'
  location: location
  name: serverfarmsName
  sku: {
    name: webAppSKU
  }
}

// Web App that hosts your bot
resource webApp 'Microsoft.Web/sites@2021-02-01' = {
  kind: 'app'
  location: location
  name: webAppName
  properties: {
    serverFarmId: serverfarm.id
    httpsOnly: true
    siteConfig: {
      alwaysOn: true
      ftpsState: 'FtpsOnly'
    }
  }
}

resource webAppSettings 'Microsoft.Web/sites/config@2021-02-01' = {
  name: '${webAppName}/appsettings'
  properties: {
    WEBSITE_NODE_DEFAULT_VERSION: '~16'
    WEBSITE_RUN_FROM_PACKAGE: '1'
    BOT_ID: botAadAppClientId
    BOT_PASSWORD: botAadAppClientSecret
    BOT_DOMAIN: webApp.properties.defaultHostName
    AAD_APP_CLIENT_ID: aadAppClientId
    AAD_APP_CLIENT_SECRET: aadAppClientSecret
    AAD_APP_TENANT_ID: aadAppTenantId
    AAD_APP_OAUTH_AUTHORITY_HOST: aadAppOauthAuthorityHost
    RUNNING_ON_AZURE: '1'
  }
}

// Register your web service as a bot with the Bot Framework
module azureBotRegistration './botRegistration/azurebot.bicep' = {
  name: 'Azure-Bot-registration'
  params: {
    resourceBaseName: resourceBaseName
    botAadAppClientId: botAadAppClientId
    botAppDomain: webApp.properties.defaultHostName
    botDisplayName: botDisplayName
  }
}

// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details.
output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id
output BOT_DOMAIN string = webApp.properties.defaultHostName

manifest.json

{
    "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
    "manifestVersion": "1.16",
    "version": "1.0.0",
    "id": "${{TEAMS_APP_ID}}",
    "packageName": "com.microsoft.teams.extension",
    "developer": {
        "name": "Teams App, Inc.",
        "websiteUrl": "https://www.example.com",
        "privacyUrl": "https://www.example.com/termofuse",
        "termsOfUseUrl": "https://www.example.com/privacy"
    },
    "icons": {
        "color": "color.png",
        "outline": "outline.png"
    },
    "name": {
        "short": "MS_Teams_Bot-PoC${{APP_NAME_SUFFIX}}",
        "full": "full name for MS_Teams_Bot-PoC"
    },
    "description": {
        "short": "Testing various concepts of Teams Bot",
        "full": "We are testing SSO this time round"
    },
    "accentColor": "#FFFFFF",
    "bots": [
        {
            "botId": "${{BOT_ID}}",
            "scopes": [
                "personal",
                "team",
                "groupchat"
            ],
            "supportsFiles": false,
            "isNotificationOnly": false
        }
    ],
    "composeExtensions": [],
    "configurableTabs": [],
    "staticTabs": [],
    "permissions": [
        "identity",
        "messageTeamMembers"
    ],
    "validDomains": ["${{BOT_DOMAIN}}",
                     "*.botframework.com"],
    "webApplicationInfo": {
        "id": "${{BOT_ID}}",
        "resource": "api://botid-${{BOT_ID}}"
    }
}
lilyydu commented 7 months ago

@BlueWhaleSEO Thank you for sharing your files! I tried following your steps but I was unable to reproduce the bug. I will try again with my team and let you know.

In the meantime, to unblock you, this is the recommended approach to add AzureOpenAI to your SSO bot. This is an example of our SSO bot with AI logic from our ChatModeration bot.

We suggest this approach rather than changes in the config, YML, azure.paramters.json and azure.bicep files.

STEPS:

  1. Clone a new version of the SSO Bot
  2. Run yarn install and yarn build in the root JS folder
  3. Add in the prompts/chat folder with your config.json and skprompt.txt (in this format)
  4. Comment out lines 104-114
  5. Add in the code snippet below to your index.ts. My model uses gpt-35-turbo and this is edited in both the index.ts and config.json
  6. In your root folder's .env file, add your associated keys (see below, similiar to this)
  7. In your teamsapp.local.yml, underneath version: 1.0.0, add environmentFolderPath: ./env
  8. Press F5 - TK automatically provisions, deploys the app for you. This will set up your bot locally on Teams.
  9. Send a series of messages to the bot, and the responses will come from the model

Let me know if this works for you.

index.ts


// Create AI components
const model = new OpenAIModel({
    // OpenAI Support
    apiKey: process.env.OPENAI_KEY!,
    defaultModel: 'gpt-3.5-turbo',

    // Azure OpenAI Support
    azureApiKey: process.env.AZURE_OPENAI_KEY!,
    azureDefaultDeployment: 'gpt-35-turbo',
    azureEndpoint: process.env.AZURE_OPENAI_ENDPOINT!,
    azureApiVersion: '2023-03-15-preview',

    // Request logging
    logRequests: true
});

const prompts = new PromptManager({
    promptsFolder: path.join(__dirname, '../src/prompts')
});

const planner = new ActionPlanner({
    model,
    prompts,
    defaultPrompt: 'chat'
});

// Create appropriate moderator
let moderator: Moderator;
if (process.env.OPENAI_KEY) {
    moderator = new OpenAIModerator({
        apiKey: process.env.OPENAI_KEY!,
        moderate: 'both'
    });
} else {
    if (!process.env.AZURE_CONTENT_SAFETY_KEY || !process.env.AZURE_CONTENT_SAFETY_ENDPOINT) {
        throw new Error(
            'Missing environment variables - please check that both AZURE_CONTENT_SAFETY_KEY and AZURE_CONTENT_SAFETY_ENDPOINT are set.'
        );
    }

    moderator = new AzureContentSafetyModerator({
        apiKey: process.env.AZURE_CONTENT_SAFETY_KEY!,
        endpoint: process.env.AZURE_CONTENT_SAFETY_ENDPOINT!,
        apiVersion: '2023-04-30-preview',
        moderate: 'both',
        categories: [
            {
                category: 'Hate',
                severity: ModerationSeverity.High
            },
            {
                category: 'SelfHarm',
                severity: ModerationSeverity.High
            },
            {
                category: 'Sexual',
                severity: ModerationSeverity.High
            },
            {
                category: 'Violence',
                severity: ModerationSeverity.High
            }
        ]
        // breakByBlocklists: true,
        // blocklistNames: [] // Text blocklist Name. Only support following characters: 0-9 A-Z a-z - . _ ~. You could attach multiple lists name here.
    });
}

// Define storage and application
const storage = new MemoryStorage();
const app = new ApplicationBuilder<ApplicationTurnState>()
    .withStorage(storage)
    .withAuthentication(adapter, {
        settings: {
            graph: {
                scopes: ['User.Read'],
                msalConfig: {
                    auth: {
                        clientId: process.env.AAD_APP_CLIENT_ID!,
                        clientSecret: process.env.AAD_APP_CLIENT_SECRET!,
                        authority: `${process.env.AAD_APP_OAUTH_AUTHORITY_HOST}/${process.env.AAD_APP_TENANT_ID}`
                    }
                },
                signInLink: `https://${process.env.BOT_DOMAIN}/auth-start.html`,
                endOnInvalidMessage: true
            }
        }
    })
    .withAIOptions({
        planner,
        moderator
    })
    .build();

app.ai.action(AI.FlaggedInputActionName, async (context, state, data) => {
    let message = '';
    if (data?.categories?.hate) {
        message += `<strong>Hate speech</strong> detected. Severity: ${data.category_scores.hate}. `;
    }
    if (data?.categories?.sexual) {
        message += `<strong>Sexual content</strong> detected. Severity: ${data.category_scores.sexual}. `;
    }
    if (data?.categories?.selfHarm) {
        message += `<strong>Self harm</strong> detected. Severity: ${data.category_scores.selfHarm}. `;
    }
    if (data?.categories?.violence) {
        message += `<strong>Violence</strong> detected. Severity: ${data.category_scores.violence}. `;
    }

    await context.sendActivity(
        `I'm sorry your message was flagged due to triggering Azure OpenAI’s content management policy. Reason: ${message}`
    );
    return AI.StopCommandName;
});

app.ai.action(AI.FlaggedOutputActionName, async (context, state, data) => {
    await context.sendActivity(`I'm not allowed to talk about such things.`);
    return AI.StopCommandName;
});

app.ai.action(AI.HttpErrorActionName, async (context, state, data) => {
    await context.sendActivity('An AI request failed. Please try again later.');
    return AI.StopCommandName;
});

.env

AZURE_OPENAI_KEY=
AZURE_OPENAI_ENDPOINT=
AZURE_CONTENT_SAFETY_KEY=
AZURE_CONTENT_SAFETY_ENDPOINT=
donatas-xyz commented 7 months ago

Hi @lilyydu.

I've finally had a chance to test your method of installing and running SSO bot. Unfortunately, it comes back with the same error.

Here are my notes on this:

  1. I've followed your instructions to the letter. I've even installed and used yarn, which I didn't need to before.
  2. I had to remove Moderator code, as I don't have it set up in Azure - I hope it's not a requirement for SSO to work?
  3. wsrun package was missing, so I had to install that additionally.
  4. Teams Toolkit provisioned the app successfully, but failed on deployment with this error Error: Script ('yarn install') execution error: warning @microsoft/teams-ai > botbuilder > @azure/msal-node@1.18.4: A newer major version of this library is available. Please upgrade to the latest available version.. So I've realised I had to downgrade my NodeJS v20 to v18 in order for @azure/msal-node@1.18.4 to work. After that the deployment went ahead.
  5. Whilst testing the bot, it logged me in via MS Entra successfully and returned the token and my last prompt back, but when I asked it a trivial question again - I've got 401 error back just like with all the other bots I've tested before. image

I haven't added anything else besides what was in your instructions and neither have I made any changes in MS Entra entity.

Just thinking out loud - could it be that msal-node 1.18 is not agreeing with the corporate "firewall" somehow? Something like this bug here: https://github.com/Azure/azure-sdk-for-js/issues/21867 ?

lilyydu commented 7 months ago

Hi @donatas-xyz,

Since you're receiving a 401 again and the bot is responding correctly for other scenarios, it has to do with the configuration of your AzureOpenAI credentials

  1. Can you verify your AzureOpenAI credentials are activated and working?
  2. When you followed my instructions, did you use your config file or a .env?
  3. For your bot, is the config file properly loading in the environment variables? This includes the azureApiKey, azureDefaultDeployment, azureEndpoint and azureApiVersion. You can try printing out the values to check

It's a bit difficult for me to debug- are you able to link me to a public repo with your code and I can try to clone + reproduce?

donatas-xyz commented 7 months ago

Hi @lilyydu.

Just to make sure everything is still working without SSO, I've created another basic AI bot using Teams Toolkit. It works as expected.

Let me try to answer your questions:

  1. I assume that if the bot can return the below, and I was also able to demo various scenarios from simple prompts to a quite complex document generation - my AzureOpenAI credentials should be active? image
  2. I'm always using using both .env.* files to store the credentials - as per instructions in documentation. To make it clear, the files end up with the following keys populated:

    .env.dev

    
    TEAMSFX_ENV=dev
    APP_NAME_SUFFIX=dev

AZURE_SUBSCRIPTION_ID= AZURE_RESOURCE_GROUP_NAME= RESOURCE_SUFFIX=

BOT_ID= TEAMS_APP_ID= BOT_AZURE_APP_SERVICE_RESOURCE_ID= BOT_DOMAIN= TEAMS_APP_TENANT_ID=

### .env.dev.user 
```text
SECRET_BOT_PASSWORD=crypto_
SECRET_OPENAI_API_KEY=
SECRET_AZURE_OPENAI_API_KEY=
SECRET_AZURE_OPENAI_ENDPOINT=
TEAMS_APP_UPDATE_TIME=
  1. I've console logged the variables you've asked about and all of them seem to come back fine. I've even went as far as adding dummy variables instead, and surely enough the whole thing went down with errors, including 401 and 404 ones.

I appreciate that having a link to a repo would be helpful (I would have to upload the files outside my company's repository though), but in this instance it's literally absolutely nothing different from the template provided by the Teams Toolkit or the example you've kindly shared. It's only my credentials that are different, so the bot itself cannot even be called "mine" as there's nothing of mine in it :)

The credentials above I don't ever change once they are set up. All that gets added is AAD config and credentials. And that's when previously working functionality starts failing with 401 error.

lilyydu commented 7 months ago

Hi @donatas-xyz,

Awesome, it does look like your credentials are active. Thank you for providing the code snippets as well.

First note- your Azure keys and endpoints are meant to be stored in the .env file in the root dir, rather than the .env* files. You can duplicate this file, populate the missing fields, and rename it to ".env". This automatically links to the YML we've set up.

I noticed you're importing a config file and populating your values inside app.ts with config's parameters. However, instead, now that we have the .env file, you can simply do this (we're usingprocess.env instead of config.):

/ Create AI components
const model = new OpenAIModel({
    // OpenAI Support
    apiKey: process.env.OPENAI_KEY!,
    defaultModel: 'gpt-3.5-turbo',

    // Azure OpenAI Support
    azureApiKey: process.env.AZURE_OPENAI_KEY!,
    azureDefaultDeployment: 'gpt-35-turbo',
    azureEndpoint: process.env.AZURE_OPENAI_ENDPOINT!,
    azureApiVersion: '2023-03-15-preview',

    // Request logging
    logRequests: true
});

I noticed you also changed the YML, azure.parameters.json and azure.bicep files, this is not necessary. These files should be the same as our source code

donatas-xyz commented 7 months ago

Hi @lilyydu,

Thank you for your reply. I assume your suggestions are mostly recommendations and not the requirements, as at the end of the day - both methods of implementing SSO should work? What I mean is that I've tried both pure and unmodified Teams Toolkit method and a method of copying ready SSO example from GitHub - both failed with the same issue.

There are not that many files and lines to add to a working AI Chatbot template in order to get SSO working. Or otherwise we are saying we can't incrementally add features to our working solutions unless we get prepared example of said solution from the GitHub :) I have definitely compared every single file and every line between various SSO examples and cannot see anything missing. It definitely made me learn a lot about how the bot app is structured, but it hasn't resolved my issue.

I think the last part to make sure is correct is credentials and MS Entra bit. So if I may ask you to confirm that these settings and assumptions are correct please? We are obviously talking about the most basic out-of-the-box SSO AI bot here.

  1. BOT_PASSWORD and AAD_APP_CLIENT_SECRET share the same value found at MS Entra > Bot App > Certificates & secrets > Value. I only have one secret in there at the moment.
  2. I'm pretty sure these get populated during the first Provision, but just in case: image
  3. BOT_ID and AAD_APP_CLIENT_ID share the same value
  4. TEAMS_APP_TENANT_ID and AAD_APP_TENANT_ID share the same value
  5. AAD_APP_OAUTH_AUTHORITY=https://login.microsoftonline.com/your-tenant-id-GUID and AAD_APP_OAUTH_AUTHORITY_HOST=https://login.microsoftonline.com
  6. AAD_APP_ACCESS_AS_USER_PERMISSION_ID comes from MS Entra > Bot App > Manifest > oauth2Permissions > id like so: image

I think besides that I have nothing else left to check and any other ideas my be long shots, such as CORS, corporate firewall and its "certificate issues" etc.

Can I also ask if anyone else managed to get SSO working by simply going fresh Teams Toolkit route as described before? And I mean in an environment that hasn't been prepared for Teams apps development before - no additional firewall ports opened, no NodeJS packages installed, Azure never really used for web apps either, no little one-off environmental hacks applied to just get things working etc. Should it really be just as easy as putting your own credentials in, provisioning and deploying, and then it all (including MS Entra settings) should just work?

Thank you!

lilyydu commented 7 months ago

Hi @donatas-xyz,

Apologies for the delay, I was OOF

  1. BOT_PASSWORD and AAD_APP_CLIENT_SECRET share different values. The one in MS Entra is theBOT_PASSSWORD
  2. Application (client ID) is not AAD_APP_CLIENT_ID. Likewise, Object ID is not AAD_APP_OBJECT_ID. Only Directory(tenant ID) = AAD_APP_TENANT_ID.
  3. BOT_ID and AAD_APP_CLIENT_ID are also different
  4. Yes
  5. Yes
  6. Yes, but for the oauth2Permissions in the aad.manifest.json file. The one in MS Entra is empty

Not to my knowledge, but going to tag @blackchoey for assistance from the Toolkit team as well- any guess to what is going wrong here?

blackchoey commented 6 months ago

Hi @donatas-xyz To my understanding, you're running your app from Azure App Service. Below bicep snippet you shared previously shows the OpenAI key is not configured to Azure App Service, can you check the App Service's configuration? It should include the endpoint and key for Azure OpenAI.

resource webAppSettings 'Microsoft.Web/sites/config@2021-02-01' = {
  name: '${webAppName}/appsettings'
  properties: {
    WEBSITE_NODE_DEFAULT_VERSION: '~16'
    WEBSITE_RUN_FROM_PACKAGE: '1'
    BOT_ID: botAadAppClientId
    BOT_PASSWORD: botAadAppClientSecret
    BOT_DOMAIN: webApp.properties.defaultHostName
    AAD_APP_CLIENT_ID: aadAppClientId
    AAD_APP_CLIENT_SECRET: aadAppClientSecret
    AAD_APP_TENANT_ID: aadAppTenantId
    AAD_APP_OAUTH_AUTHORITY_HOST: aadAppOauthAuthorityHost
    RUNNING_ON_AZURE: '1'
  }
}

As far as I know, MSAL does not use axios to send requests. Since you get an axios error, we may focus on the http requests to Azure OpenAI first. Please let me know if I misunderstand the problem.

donatas-xyz commented 6 months ago

Hi @blackchoey. Thank you for getting back to me.

The azure.bicep snippet above is no different to the one found SSO bot example, so are you saying that additional keys of AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT should be added to it?

Thank you!

blackchoey commented 6 months ago

Yes, the example you referred does not include AI capability, so it doesn't have the settings. Before you add the AI related app settings, please check your code and update the setting's name accordingly if your code is referencing another setting name.

donatas-xyz commented 6 months ago

Well of course it had to be something as stupid as this... It finally works @blackchoey @lilyydu! Thank you!

Besides other questions that this raises, perhaps it would be a good idea to update the SSO bot example with those 2 lines as well, because it's really difficult to consider it a working example under teams-ai repo, when all it can do is to retrieve the Graph key... A simple console app can do that :)

Here's what I had to do in the end:

azure.bicep

@secure()
param azureOpenAIKey string

@secure()
param azureOpenAIEndpoint string

// [..]

resource webAppSettings 'Microsoft.Web/sites/config@2021-02-01' = {
  name: '${webAppName}/appsettings'
  properties: {
    WEBSITE_NODE_DEFAULT_VERSION: '~18'
    WEBSITE_RUN_FROM_PACKAGE: '1'
    BOT_ID: botAadAppClientId
    BOT_PASSWORD: botAadAppClientSecret
    BOT_DOMAIN: webApp.properties.defaultHostName
    AAD_APP_CLIENT_ID: aadAppClientId
    AAD_APP_CLIENT_SECRET: aadAppClientSecret
    AAD_APP_TENANT_ID: aadAppTenantId
    AAD_APP_OAUTH_AUTHORITY_HOST: aadAppOauthAuthorityHost
    AZURE_OPENAI_API_KEY: azureOpenAIKey 
    AZURE_OPENAI_ENDPOINT: azureOpenAIEndpoint
    RUNNING_ON_AZURE: '1'
  }
}

Thank you once again for your help and your patience!

Now onto the #1130 bug, that's still stopping me from using teams-ai v1.