Skip to content

Commit 6392a8c

Browse files
mattgotteinerpamelafoxMatt Gotteiner
authored
Automate Login Setup (#891)
* Adding support for automating login setup * Remove msal_extensions, use in-memory token cache * Authentication refactor --------- Co-authored-by: Pamela Fox <[email protected]> Co-authored-by: Pamela Fox <[email protected]> Co-authored-by: Matt Gotteiner <[email protected]>
1 parent 5e323c2 commit 6392a8c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+885
-181
lines changed

LoginAndAclSetup.md

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- [Requirements](#requirements)
66
- [Setting up Azure AD Apps](#setting-up-azure-ad-apps)
7+
- [Automatic Setup](#automatic-setup)
78
- [Manual Setup](#manual-setup)
89
- [Server App](#server-app)
910
- [Client App](#client-app)
@@ -15,6 +16,7 @@
1516
- [Azure Data Lake Storage Gen2 Prep Docs](#azure-data-lake-storage-gen2-prep-docs)
1617
- [Manually managing Document Level Access Control](#manually-managing-document-level-access-control)
1718
- [Environment Variables Reference](#environment-variables-reference)
19+
- [Authentication Behavior by Environment](#authentication-behavior-by-environment)
1820

1921
This guide demonstrates how to add an optional login and document level access control system to the sample. This system can be used to restrict access to indexed data to specific users based on what [Azure Active Directory (Azure AD) groups](https://learn.microsoft.com/azure/active-directory/fundamentals/how-to-manage-groups) they are a part of, or their [user object id](https://learn.microsoft.com/partner-center/find-ids-and-domain-names#find-the-user-object-id).
2022

@@ -30,6 +32,16 @@ This guide demonstrates how to add an optional login and document level access c
3032

3133
Two Azure AD apps must be registered in order to make the optional login and document level access control system work correctly. One app is for the client UI. The client UI is implemented as a [single page application](https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration). The other app is for the API server. The API server uses a [confidential client](https://learn.microsoft.com/azure/active-directory/develop/msal-client-applications) to call the [Microsoft Graph API](https://learn.microsoft.com/graph/use-the-api).
3234

35+
### Automatic Setup
36+
37+
The easiest way to setup the two apps is to use the `azd` CLI. We've written scripts that will automatically create the two apps and configure them for use with the sample. To trigger the automatic setup, run the following commands:
38+
39+
1. Run `azd env set AZURE_USE_AUTHENTICATION true` to enable the login UI and App Service authentication.
40+
1. Ensure access control is enabled on your search index. If your index doesn't exist yet, run prepdocs with `AZURE_USE_AUTHENTICATION` set to `true`. If your index already exists, run `./scrips/manageacl.ps1 --acl-action enable_acls`.
41+
1. (Optional) To require access control when using the app, run `azd env set AZURE_ENFORCE_ACCESS_CONTROL true`.
42+
1. Run `azd env set AZURE_AUTH_TENANT_ID <YOUR-TENANT-ID>` to set the tenant ID associated with authentication.
43+
2. Run `azd up` to deploy the app.
44+
3345
### Manual Setup
3446

3547
The following instructions explain how to setup the two apps using the Azure Portal.
@@ -40,15 +52,15 @@ The following instructions explain how to setup the two apps using the Azure Por
4052
* Select the Azure AD Service.
4153
* In the left hand menu, select **Application Registrations**.
4254
* Select **New Registration**.
43-
* In the **Name** section, enter a meaningful application name. This name will be displayed to users of the app, for example `Azure Search OpenAI Demo API`.
55+
* In the **Name** section, enter a meaningful application name. This name will be displayed to users of the app, for example `Azure Search OpenAI Chat API`.
4456
* Under **Supported account types**, select **Accounts in this organizational directory only**.
4557
* Select **Register** to create the application
4658
* In the app's registration screen, find the **Application (client) ID**.
4759
* Run the following `azd` command to save this ID: `azd env set AZURE_SERVER_APP_ID <Application (client) ID>`.
4860
* Azure Active Directory (Azure AD) supports three types of credentials to authenticate an app using the [client credentials](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow): passwords (app secrets), certificates, and federated identity credentials. For a higher level of security, either [certificates](https://learn.microsoft.com/azure/active-directory/develop/howto-create-self-signed-certificate) or federated identity credentials are recommended. This sample currently uses an app secret for ease of provisioning.
4961
* Select **Certificates & secrets** in the left hand menu.
5062
* In the **Client secrets** section, select **New client secret**.
51-
* Type a description, for example `Azure Search OpenAI Demo Key`.
63+
* Type a description, for example `Azure Search OpenAI Chat Key`.
5264
* Select one of the available key durations.
5365
* The generated key value will be displayed after you select **Add**.
5466
* Copy the generated key value and run the following `azd` command to save this ID: `azd env set AZURE_SERVER_APP_SECRET <generated key value>`.
@@ -64,10 +76,10 @@ The following instructions explain how to setup the two apps using the Azure Por
6476
* Fill in the values as indicated:
6577
* For **Scope name**, use **access_as_user**.
6678
* For **Who can consent?**, select **Admins and users**.
67-
* For **Admin consent display name**, type **Access Azure Search OpenAI Demo API**.
68-
* For **Admin consent description**, type **Allows the app to access Azure Search OpenAI Demo API as the signed-in user.**.
69-
* For **User consent display name**, type **Access Azure Search OpenAI Demo API**.
70-
* For **User consent description**, type **Allow the app to access Azure Search OpenAI Demo API on your behalf**.
79+
* For **Admin consent display name**, type **Access Azure Search OpenAI Chat API**.
80+
* For **Admin consent description**, type **Allows the app to access Azure Search OpenAI Chat API as the signed-in user.**.
81+
* For **User consent display name**, type **Access Azure Search OpenAI Chat API**.
82+
* For **User consent description**, type **Allow the app to access Azure Search OpenAI Chat API on your behalf**.
7183
* Leave **State** set to **Enabled**.
7284
* Select **Add scope** at the bottom to save the scope.
7385
* (Optional) Enable group claims. Include which Azure AD groups the user is part of as part of the login in the [optional claims](https://learn.microsoft.com/azure/active-directory/develop/optional-claims). The groups are used for [optional security filtering](https://learn.microsoft.com/azure/search/search-security-trimming-for-azure-search) in the search results.
@@ -83,7 +95,7 @@ The following instructions explain how to setup the two apps using the Azure Por
8395
* Select the Azure AD Service.
8496
* In the left hand menu, select **Application Registrations**.
8597
* Select **New Registration**.
86-
* In the **Name** section, enter a meaningful application name. This name will be displayed to users of the app, for example `Azure Search OpenAI Demo Web App`.
98+
* In the **Name** section, enter a meaningful application name. This name will be displayed to users of the app, for example `Azure Search OpenAI Chat Web App`.
8799
* Under **Supported account types**, select **Accounts in this organizational directory only**.
88100
* Under `Redirect URI (optional)` section, select `Single-page application (SPA)` in the combo-box and enter the following redirect URI:
89101
* If you are running the sample locally, use `http://localhost:50505/redirect`.
@@ -97,7 +109,7 @@ The following instructions explain how to setup the two apps using the Azure Por
97109
* Select **Save**
98110
* In the left hand menu, select **API permissions**. You will add permission to access the **access_as_user** API on the server app. This permission is required for the [On Behalf Of Flow](https://learn.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#protocol-diagram) to work.
99111
* Select **Add a permission**, and then **My APIs**.
100-
* In the list of applications, select your server application **Azure Search OpenAI Demo API**
112+
* In the list of applications, select your server application **Azure Search OpenAI Chat API**
101113
* Ensure **Delegated permissions** is selected.
102114
* In the **Select permissions** section, select the **access_as_user** permission
103115
* Select **Add permissions**.
@@ -166,8 +178,8 @@ Manually enable document level access control on a search index and manually set
166178
Run `azd up` or use `azd env set` to manually set `AZURE_SEARCH_SERVICE` and `AZURE_SEARCH_INDEX` environment variables prior to running the script.
167179

168180
The script supports the following commands. Note that the syntax is the same regardless of whether [manageacl.ps1](./scripts/manageacl.ps1) or [manageacl.sh](./scripts/manageacl.sh) is used.
169-
* `./scripts/manageacls.ps1 --enable-acls`: Creates the required `oids` (User ID) and `groups` (Group IDs) [security filter](https://learn.microsoft.com/azure/search/search-security-trimming-for-azure-search) fields for document level access control on your index. Does nothing if these fields already exist.
170-
* Example usage: `./scripts/manageacls.ps1 --enable-acls`
181+
* `./scripts/manageacls.ps1 --acl-action enable_acls`: Creates the required `oids` (User ID) and `groups` (Group IDs) [security filter](https://learn.microsoft.com/azure/search/search-security-trimming-for-azure-search) fields for document level access control on your index. Does nothing if these fields already exist.
182+
* Example usage: `../scripts/manageacls.ps1 --acl-action enable_acls`
171183
* `./scripts/manageacls.ps1 --document [name-of-pdf.pdf] --acl-type [oids or groups]--acl-action view`: Prints access control values associated with either User IDs or Group IDs for a specific document.
172184
* Example to view all Group IDs from the Benefit_Options PDF: `./scripts/manageacls.ps1 --document Benefit_Options.pdf --acl-type oids --acl-action view`.
173185
* `./scripts/manageacls.ps1 --document [name-of-pdf.pdf] --acl-type [oids or groups]--acl-action add --acl [ID of group or user]`: Adds an access control value associated with either User IDs or Group IDs for a specific document.
@@ -182,10 +194,26 @@ The script supports the following commands. Note that the syntax is the same reg
182194
The following environment variables are used to setup the optional login and document level access control:
183195

184196
* `AZURE_USE_AUTHENTICATION`: Enables Azure AD based optional login and document level access control. Set to true before running `azd up`.
197+
* `AZURE_ENFORCE_ACCESS_CONTROL`: Makes Azure AD based login and document level access control required instead of optional. There is no way to use the app without an authenticated account. Set to true before running `azd up`
185198
* `AZURE_SERVER_APP_ID`: (Required) Application ID of the Azure AD app for the API server.
186199
* `AZURE_SERVER_APP_SECRET`: [Client secret](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow) used by the API server to authenticate using the Azure AD API server app.
187200
* `AZURE_CLIENT_APP_ID`: Application ID of the Azure AD app for the client UI.
188-
* `AZURE_TENANT_ID`: [Tenant ID](https://learn.microsoft.com/azure/active-directory/fundamentals/how-to-find-tenant) associated with the Azure AD used for login and document level access control. This is set automatically by `azd up`.
201+
* `AZURE_AUTH_TENANT_ID`: [Tenant ID](https://learn.microsoft.com/azure/active-directory/fundamentals/how-to-find-tenant) associated with the Azure AD used for login and document level access control. Defaults to `AZURE_TENANT_ID` if not defined.
189202
* `AZURE_ADLS_GEN2_STORAGE_ACCOUNT`: (Optional) Name of existing [Data Lake Storage Gen2 storage account](https://learn.microsoft.com/azure/storage/blobs/data-lake-storage-introduction) for storing sample data with [access control lists](https://learn.microsoft.com/azure/storage/blobs/data-lake-storage-access-control). Only used with the optional Data Lake Storage Gen2 [setup](#azure-data-lake-storage-gen2-setup) and [prep docs](#azure-data-lake-storage-gen2-prep-docs) scripts.
190203
* `AZURE_ADLS_GEN2_STORAGE_FILESYSTEM`: (Optional) Name of existing [Data Lake Storage Gen2 filesystem](https://learn.microsoft.com/azure/storage/blobs/data-lake-storage-introduction) for storing sample data with [access control lists](https://learn.microsoft.com/azure/storage/blobs/data-lake-storage-access-control). Only used with the optional Data Lake Storage Gen2 [setup](#azure-data-lake-storage-gen2-setup) and [prep docs](#azure-data-lake-storage-gen2-prep-docs) scripts.
191204
* `AZURE_ADLS_GEN2_STORAGE_FILESYSTEM_PATH`: (Optional) Name of existing path in a [Data Lake Storage Gen2 filesystem](https://learn.microsoft.com/azure/storage/blobs/data-lake-storage-introduction) for storing sample data with [access control lists](https://learn.microsoft.com/azure/storage/blobs/data-lake-storage-access-control). Only used with the optional Data Lake Storage Gen2 [prep docs](#azure-data-lake-storage-gen2-prep-docs) script.
205+
206+
### Authentication behavior by environment
207+
208+
This application uses an in-memory token cache. User sessions are only available in memory while the application is running. When the application server is restarted, all users will need to log-in again.
209+
210+
The following table describes the impact of the `AZURE_USE_AUTHENTICATION` and `AZURE_ENFORCE_ACCESS_CONTROL` variables depending on the environment you are deploying the application in:
211+
212+
| AZURE_USE_AUTHENTICATION | AZURE_ENFORCE_ACCESS_CONTROL | Environment | Default Behavior |
213+
|-|-|-|-|
214+
| True | False | App Services | Use integrated auth <br /> Login page blocks access to app <br /> User can opt-into access control in developer settings <br /> Allows unrestricted access to sources |
215+
| True | True | App Services | Use integrated auth <br /> Login page blocks access to app <br /> User must use access control |
216+
| True | False | Local or Codespaces | Do not use integrated auth <br /> Can use app without login <br /> User can opt-into access control in developer settings <br /> Allows unrestricted access to sources |
217+
| True | True | Local or Codespaces | Do not use integrated auth <br /> Cannot use app without login <br /> Behavior is chat box is greyed out with default “Please login message” <br /> User must use login button to make chat box usable <br /> User must use access control when logged in |
218+
| False | False | All | No login or access control |
219+
| False | True | All | Invalid setting |

app/backend/app.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from azure.keyvault.secrets.aio import SecretClient
1313
from azure.monitor.opentelemetry import configure_azure_monitor
1414
from azure.search.documents.aio import SearchClient
15+
from azure.search.documents.indexes.aio import SearchIndexClient
1516
from azure.storage.blob.aio import BlobServiceClient
1617
from openai import APIError, AsyncAzureOpenAI, AsyncOpenAI
1718
from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor
@@ -129,8 +130,8 @@ async def ask():
129130
request_json = await request.get_json()
130131
context = request_json.get("context", {})
131132
auth_helper = current_app.config[CONFIG_AUTH_CLIENT]
132-
context["auth_claims"] = await auth_helper.get_auth_claims_if_enabled(request.headers)
133133
try:
134+
context["auth_claims"] = await auth_helper.get_auth_claims_if_enabled(request.headers)
134135
use_gpt4v = context.get("overrides", {}).get("use_gpt4v", False)
135136
approach: Approach
136137
if use_gpt4v and CONFIG_ASK_VISION_APPROACH in current_app.config:
@@ -168,9 +169,8 @@ async def chat():
168169
request_json = await request.get_json()
169170
context = request_json.get("context", {})
170171
auth_helper = current_app.config[CONFIG_AUTH_CLIENT]
171-
context["auth_claims"] = await auth_helper.get_auth_claims_if_enabled(request.headers)
172-
173172
try:
173+
context["auth_claims"] = await auth_helper.get_auth_claims_if_enabled(request.headers)
174174
use_gpt4v = context.get("overrides", {}).get("use_gpt4v", False)
175175
approach: Approach
176176
if use_gpt4v and CONFIG_CHAT_VISION_APPROACH in current_app.config:
@@ -230,12 +230,14 @@ async def setup_clients():
230230
# Used only with non-Azure OpenAI deployments
231231
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
232232
OPENAI_ORGANIZATION = os.getenv("OPENAI_ORGANIZATION")
233+
234+
AZURE_TENANT_ID = os.getenv("AZURE_TENANT_ID")
233235
AZURE_USE_AUTHENTICATION = os.getenv("AZURE_USE_AUTHENTICATION", "").lower() == "true"
236+
AZURE_ENFORCE_ACCESS_CONTROL = os.getenv("AZURE_ENFORCE_ACCESS_CONTROL", "").lower() == "true"
234237
AZURE_SERVER_APP_ID = os.getenv("AZURE_SERVER_APP_ID")
235238
AZURE_SERVER_APP_SECRET = os.getenv("AZURE_SERVER_APP_SECRET")
236239
AZURE_CLIENT_APP_ID = os.getenv("AZURE_CLIENT_APP_ID")
237-
AZURE_TENANT_ID = os.getenv("AZURE_TENANT_ID")
238-
TOKEN_CACHE_PATH = os.getenv("TOKEN_CACHE_PATH")
240+
AZURE_AUTH_TENANT_ID = os.getenv("AZURE_AUTH_TENANT_ID", AZURE_TENANT_ID)
239241

240242
KB_FIELDS_CONTENT = os.getenv("KB_FIELDS_CONTENT", "content")
241243
KB_FIELDS_SOURCEPAGE = os.getenv("KB_FIELDS_SOURCEPAGE", "sourcepage")
@@ -251,27 +253,32 @@ async def setup_clients():
251253
# If you encounter a blocking error during a DefaultAzureCredential resolution, you can exclude the problematic credential by using a parameter (ex. exclude_shared_token_cache_credential=True)
252254
azure_credential = DefaultAzureCredential(exclude_shared_token_cache_credential=True)
253255

254-
# Set up authentication helper
255-
auth_helper = AuthenticationHelper(
256-
use_authentication=AZURE_USE_AUTHENTICATION,
257-
server_app_id=AZURE_SERVER_APP_ID,
258-
server_app_secret=AZURE_SERVER_APP_SECRET,
259-
client_app_id=AZURE_CLIENT_APP_ID,
260-
tenant_id=AZURE_TENANT_ID,
261-
token_cache_path=TOKEN_CACHE_PATH,
262-
)
263-
264256
# Set up clients for AI Search and Storage
265257
search_client = SearchClient(
266258
endpoint=f"https://{AZURE_SEARCH_SERVICE}.search.windows.net",
267259
index_name=AZURE_SEARCH_INDEX,
268260
credential=azure_credential,
269261
)
262+
search_index_client = SearchIndexClient(
263+
endpoint=f"https://{AZURE_SEARCH_SERVICE}.search.windows.net",
264+
credential=azure_credential,
265+
)
270266
blob_client = BlobServiceClient(
271267
account_url=f"https://{AZURE_STORAGE_ACCOUNT}.blob.core.windows.net", credential=azure_credential
272268
)
273269
blob_container_client = blob_client.get_container_client(AZURE_STORAGE_CONTAINER)
274270

271+
# Set up authentication helper
272+
auth_helper = AuthenticationHelper(
273+
search_index=(await search_index_client.get_index(AZURE_SEARCH_INDEX)) if AZURE_USE_AUTHENTICATION else None,
274+
use_authentication=AZURE_USE_AUTHENTICATION,
275+
server_app_id=AZURE_SERVER_APP_ID,
276+
server_app_secret=AZURE_SERVER_APP_SECRET,
277+
client_app_id=AZURE_CLIENT_APP_ID,
278+
tenant_id=AZURE_AUTH_TENANT_ID,
279+
require_access_control=AZURE_ENFORCE_ACCESS_CONTROL,
280+
)
281+
275282
vision_key = None
276283
if VISION_SECRET_NAME and AZURE_KEY_VAULT_NAME: # Cognitive vision keys are stored in keyvault
277284
key_vault_client = SecretClient(
@@ -310,6 +317,7 @@ async def setup_clients():
310317
current_app.config[CONFIG_ASK_APPROACH] = RetrieveThenReadApproach(
311318
search_client=search_client,
312319
openai_client=openai_client,
320+
auth_helper=auth_helper,
313321
chatgpt_model=OPENAI_CHATGPT_MODEL,
314322
chatgpt_deployment=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
315323
embedding_model=OPENAI_EMB_MODEL,
@@ -328,6 +336,7 @@ async def setup_clients():
328336
search_client=search_client,
329337
openai_client=openai_client,
330338
blob_container_client=blob_container_client,
339+
auth_helper=auth_helper,
331340
vision_endpoint=AZURE_VISION_ENDPOINT,
332341
vision_key=vision_key,
333342
gpt4v_deployment=AZURE_OPENAI_GPT4V_DEPLOYMENT,
@@ -344,6 +353,7 @@ async def setup_clients():
344353
search_client=search_client,
345354
openai_client=openai_client,
346355
blob_container_client=blob_container_client,
356+
auth_helper=auth_helper,
347357
vision_endpoint=AZURE_VISION_ENDPOINT,
348358
vision_key=vision_key,
349359
gpt4v_deployment=AZURE_OPENAI_GPT4V_DEPLOYMENT,
@@ -359,6 +369,7 @@ async def setup_clients():
359369
current_app.config[CONFIG_CHAT_APPROACH] = ChatReadRetrieveReadApproach(
360370
search_client=search_client,
361371
openai_client=openai_client,
372+
auth_helper=auth_helper,
362373
chatgpt_model=OPENAI_CHATGPT_MODEL,
363374
chatgpt_deployment=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
364375
embedding_model=OPENAI_EMB_MODEL,

0 commit comments

Comments
 (0)