Skip to content

Commit d07987e

Browse files
committed
Add MSI authentication to CQA sample
1 parent 55d1f53 commit d07987e

File tree

4 files changed

+61
-18
lines changed

4 files changed

+61
-18
lines changed

samples/javascript_nodejs/48.customQABot-all-features/.env

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ MicrosoftAppTenantId=
55

66
ProjectName=
77
LanguageEndpointKey=
8+
LanguageManagedIdentityClientId=
89
LanguageEndpointHostName=
910
DefaultAnswer=
1011
DefaultWelcomeMessage=
1112
EnablePreciseAnswer= true
1213
DisplayPreciseAnswerOnly= false
13-
UseTeamsAdaptiveCard= false
14+
UseTeamsAdaptiveCard= false

samples/javascript_nodejs/48.customQABot-all-features/README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,36 @@ This bot was created using [Bot Framework][BF].
2929
- Go to `Deploy knowledge base` and click on `Deploy`.
3030
3131
### Connect your bot to the project.
32-
Follow these steps to update [.env file](.env).
32+
There are two ways the bot could authenticate to the Language resource.
33+
34+
Pick one and follow these steps to update [.env file](.env) accordingly.
35+
36+
1. Using an `Endpoint Key`: _provides an easier configuration by using a secret. Great way to test the bot locally_.
37+
3338
- In the [Azure Portal][Azure], go to your resource.
3439
- Go to `Keys and Endpoint` under Resource Management.
3540
- Copy one of the keys as value of `LanguageEndpointKey` and Endpoint as value of `LanguageEndpointHostName` in [.env file](.env).
3641
- `ProjectName` is the name of the project created in [Language Studio][LS].
42+
- `LanguageManagedIdentityClientId` is not needed when using an Endpoint Key, so you can remove it from [.env file](.env).
43+
44+
2. Using a `User Managed Identity` resource: _provides a more complex configuration by using a User Managed Identity resource. Great way to authenticate without the need of a secret_.
45+
- Create a [User Managed Identity][create-msi] resource in the same region as the Language resource.
46+
- Copy the `ClientId` as value of `LanguageManagedIdentityClientId` in [.env file](.env).
47+
- In the [Azure Portal][Azure], go to the WebApp resource, where the bot is hosted.
48+
- Go to `Identity` under Settings and select `User assigned`. More information on Identity assignment can be found [here][webapp-msi].
49+
- Click on `Add` and select the User Managed Identity created in the previous step.
50+
- Click `Save` to assign the User Managed Identity to the WebApp resource.
51+
- This will allow the WebApp to communicate with the Language resource using the User Managed Identity.
52+
- In the [Azure Portal][Azure], go to the Language resource.
53+
- Assign the following role in the `Access Control (IAM)` section. More information on role assignment can be found [here][language-custom-role].
54+
- `Cognitive Services User`: _this role is required so the Managed Identity can access the keys of the Cognitive Service resource_.
55+
- In the Language resource, go to `Keys and Endpoint` under Resource Management.
56+
- Copy the `Endpoint` as value of `LanguageEndpointHostName` in [.env file](.env).
57+
- `ProjectName` is the name of the project created in [Language Studio][LS].
58+
- `LanguageEndpointKey` is not needed when using a User Managed Identity, so you can remove it from [.env file](.env).
59+
60+
> [!NOTE]
61+
> This method requires [the bot to be deployed in Azure][deploy-bot], so the User Managed Identity can authenticate to the Language resource to get access to the keys.
3762
3863
## To try this sample
3964
@@ -188,3 +213,7 @@ If you are new to Microsoft Azure, please refer to [Getting started with Azure][
188213
[Quickstart]: https://docs.microsoft.com/azure/cognitive-services/language-service/question-answering/quickstart/sdk
189214
[Azure]: https://portal.azure.com/
190215
[BFE]: https://github.com/Microsoft/BotFramework-Emulator/releases
216+
[deploy-bot]: #deploy-the-bot-to-azure
217+
[create-msi]: https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities?pivots=identity-mi-methods-azp#create-a-user-assigned-managed-identity
218+
[language-custom-role]: https://learn.microsoft.com/en-us/azure/operator-service-manager/how-to-create-user-assigned-managed-identity#assign-custom-role-1
219+
[webapp-msi]: https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal%2Chttp

samples/javascript_nodejs/48.customQABot-all-features/dialogs/rootDialog.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ const INCLUDE_UNSTRUCTURED_SOURCES = true;
2828
/**
2929
* Creates QnAMakerDialog instance with provided configuraton values.
3030
*/
31-
const createQnAMakerDialog = (knowledgeBaseId, endpointKey, endpointHostName, defaultAnswer, enablePreciseAnswerRaw, displayPreciseAnswerOnlyRaw, useTeamsAdaptiveCard) => {
31+
const createQnAMakerDialog = (knowledgeBaseId, endpointKey, managedIdentityClientId, endpointHostName, defaultAnswer, enablePreciseAnswerRaw, displayPreciseAnswerOnlyRaw, useTeamsAdaptiveCard) => {
3232
let noAnswerActivity;
3333
if (typeof defaultAnswer === 'string' && defaultAnswer !== '') {
3434
noAnswerActivity = MessageFactory.text(defaultAnswer);
3535
}
3636

3737
const qnaMakerDialog = new QnAMakerDialog(
3838
knowledgeBaseId,
39-
endpointKey,
39+
undefined,
4040
endpointHostName,
4141
// @ts-ignore
4242
noAnswerActivity,
@@ -48,6 +48,12 @@ const createQnAMakerDialog = (knowledgeBaseId, endpointKey, endpointHostName, de
4848
RANKER_TYPE
4949
);
5050

51+
if (managedIdentityClientId) {
52+
qnaMakerDialog.withManagedIdentityClientId(managedIdentityClientId);
53+
} else {
54+
qnaMakerDialog.withEndpointKey(endpointKey);
55+
}
56+
5157
qnaMakerDialog.enablePreciseAnswer = enablePreciseAnswerRaw === 'true';
5258
qnaMakerDialog.displayPreciseAnswerOnly = displayPreciseAnswerOnlyRaw === 'true';
5359
qnaMakerDialog.qnaServiceType = ServiceType.language;
@@ -63,21 +69,22 @@ class RootDialog extends ComponentDialog {
6369
/**
6470
* Root dialog for this bot. Creates a QnAMakerDialog.
6571
* @param {string} knowledgeBaseId Knowledge Base ID of the QnA Maker instance.
66-
* @param {string} endpointKey Endpoint key needed to query QnA Maker.
72+
* @param {any} endpointKey (optional) Endpoint key needed to query QnA Maker.
73+
* @param {any} managedIdentityClientId (optional) Managed identity client ID to use for authentication.
6774
* @param {string} endpointHostName Host name of the QnA Maker instance.
6875
* @param {string} defaultAnswer (optional) Text used to create a fallback response when QnA Maker doesn't have an answer for a question.
6976
* @param {string} enablePreciseAnswer
7077
* @param {string} displayPreciseAnswerOnly
7178
7279
*/
73-
constructor(knowledgeBaseId, endpointKey, endpointHostName, defaultAnswer, enablePreciseAnswer, displayPreciseAnswerOnly, useTeamsAdaptiveCard) {
80+
constructor(knowledgeBaseId, endpointKey, managedIdentityClientId, endpointHostName, defaultAnswer, enablePreciseAnswer, displayPreciseAnswerOnly, useTeamsAdaptiveCard) {
7481
super(ROOT_DIALOG);
7582
// Initial waterfall dialog.
7683
this.addDialog(new WaterfallDialog(INITIAL_DIALOG, [
7784
this.startInitialDialog.bind(this)
7885
]));
7986

80-
this.addDialog(createQnAMakerDialog(knowledgeBaseId, endpointKey, endpointHostName, defaultAnswer, enablePreciseAnswer, displayPreciseAnswerOnly, useTeamsAdaptiveCard));
87+
this.addDialog(createQnAMakerDialog(knowledgeBaseId, endpointKey, managedIdentityClientId, endpointHostName, defaultAnswer, enablePreciseAnswer, displayPreciseAnswerOnly, useTeamsAdaptiveCard));
8188
this.initialDialogId = INITIAL_DIALOG;
8289
}
8390

samples/javascript_nodejs/48.customQABot-all-features/index.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,16 @@ const restify = require('restify');
1616

1717
// Import required bot services.
1818
// See https://aka.ms/bot-services to learn more about the different parts of a bot.
19-
const { BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } = require('botbuilder');
19+
const { CloudAdapter, ConversationState, MemoryStorage, UserState, ConfigurationBotFrameworkAuthentication } = require('botbuilder');
2020

2121
const { CustomQABot } = require('./bots/CustomQABot');
2222
const { RootDialog } = require('./dialogs/rootDialog');
2323

24+
const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(process.env);
25+
2426
// Create adapter.
25-
// See https://aka.ms/about-bot-adapter to learn more about adapters.
26-
const adapter = new BotFrameworkAdapter({
27-
appId: process.env.MicrosoftAppId,
28-
appPassword: process.env.MicrosoftAppPassword
29-
});
27+
// See https://aka.ms/about-bot-adapter to learn more about how bots work.
28+
const adapter = new CloudAdapter(botFrameworkAuthentication);
3029

3130
// Catch-all for errors.
3231
adapter.onTurnError = async (context, error) => {
@@ -65,13 +64,20 @@ if (!endpointHostName?.startsWith('https://')) {
6564
endpointHostName = 'https://' + endpointHostName;
6665
}
6766

67+
const managedIdentityClientId = process.env.LanguageManagedIdentityClientId;
68+
6869
// To support backward compatibility for Key Names, fallback to process.env.QnAAuthKey.
6970
const endpointKey = process.env.LanguageEndpointKey || process.env.QnAAuthKey;
7071

72+
if (!managedIdentityClientId?.trim() && !endpointKey?.trim()) {
73+
throw new Error('Either LanguageManagedIdentityClientId or LanguageEndpointKey should be set.');
74+
}
75+
7176
// Create the main dialog.
7277
const dialog = new RootDialog(
7378
process.env.ProjectName ?? '',
74-
endpointKey ?? '',
79+
endpointKey,
80+
managedIdentityClientId,
7581
endpointHostName,
7682
process.env.DefaultAnswer ?? '',
7783
process.env.EnablePreciseAnswer?.toLowerCase() ?? 'false',
@@ -83,6 +89,8 @@ const bot = new CustomQABot(conversationState, userState, dialog);
8389

8490
// Create HTTP server.
8591
const server = restify.createServer();
92+
server.use(restify.plugins.bodyParser());
93+
8694
server.listen(process.env.port || process.env.PORT || 3978, function() {
8795
console.log(`\n${ server.name } listening to ${ server.url }.`);
8896
console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');
@@ -91,8 +99,6 @@ server.listen(process.env.port || process.env.PORT || 3978, function() {
9199

92100
// Listen for incoming requests.
93101
server.post('/api/messages', async (req, res) => {
94-
adapter.processActivity(req, res, async (turnContext) => {
95-
// Route the message to the bot's main handler.
96-
await bot.run(turnContext);
97-
});
102+
// Route received a request to adapter for processing
103+
await adapter.process(req, res, (context) => bot.run(context));
98104
});

0 commit comments

Comments
 (0)