Skip to content

Commit 02c9603

Browse files
Merge pull request #231966 from cephalin/sqlaad
SQLDB as-user connectivity tutorial
2 parents 4e7c25b + 23301f0 commit 02c9603

File tree

6 files changed

+306
-4
lines changed

6 files changed

+306
-4
lines changed
92.2 KB
Loading
16.3 KB
Loading
73.8 KB
Loading

articles/app-service/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@
122122
href: scenario-secure-app-access-microsoft-graph-as-user.md
123123
- name: JavaScript
124124
href: tutorial-connect-app-access-microsoft-graph-as-user-javascript.md
125+
- name: App to SQL Database
126+
href: tutorial-connect-app-access-sql-database-as-user-dotnet.md
125127
- name: App to app authentication
126128
href: tutorial-auth-aad.md
127129
- name: App to app to another Azure service
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
---
2+
title: 'Tutorial - Web app accesses SQL Database as the user'
3+
description: Secure database connectivity with Azure Active Directory authentication from .NET web app, using the signed-in user. Learn how to apply it to other Azure services.
4+
author: cephalin
5+
6+
ms.service: app-service
7+
ms.workload: identity
8+
ms.author: cephalin
9+
ms.devlang: csharp
10+
ms.topic: tutorial
11+
ms.date: 04/21/2023
12+
---
13+
# Tutorial: Connect an App Service app to SQL Database on behalf of the signed-in user
14+
15+
This tutorial shows you how to enable [built-in authentication](overview-authentication-authorization.md) in an [App Service](overview.md) app using the Azure Active Directory authentication provider, then extend it by connecting it to a back-end Azure SQL Database by impersonating the signed-in user (also known as the [on-behalf-of flow](../active-directory/develop/v2-oauth2-on-behalf-of-flow.md)). This is a more advanced connectivity approach to [Tutorial: Access data with managed identity](tutorial-connect-msi-sql-database.md) and has the following advantages in enterprise scenarios:
16+
17+
- Eliminates connection secrets to back-end services, just like the managed identity approach.
18+
- Gives the back-end database (or any other Azure service) more control over who or how much to grant access to its data and functionality.
19+
- Lets the app tailor its data presentation to the signed-in user.
20+
21+
In this tutorial, you add Azure Active Directory authentication to the sample web app you deployed in one of the following tutorials:
22+
23+
- [Tutorial: Build an ASP.NET app in Azure with Azure SQL Database](app-service-web-tutorial-dotnet-sqldatabase.md)
24+
- [Tutorial: Build an ASP.NET Core and Azure SQL Database app in Azure App Service](tutorial-dotnetcore-sqldb-app.md)
25+
26+
When you're finished, your sample app will authenticate users connect to SQL Database securely on behalf of the signed-in user.
27+
28+
:::image type="content" source="./media/tutorial-connect-app-access-sql-database-as-user-dotnet/architecture.png" alt-text="Architecture diagram for tutorial scenario.":::
29+
30+
> [!NOTE]
31+
> The steps covered in this tutorial support the following versions:
32+
>
33+
> - .NET Framework 4.8 and higher
34+
> - .NET 6.0 and higher
35+
>
36+
37+
What you will learn:
38+
39+
> [!div class="checklist"]
40+
> * Enable built-in authentication for Azure SQL Database
41+
> * Disable other authentication options in Azure SQL Database
42+
> * Enable App Service authentication
43+
> * Use Azure Active Directory as the identity provider
44+
> * Access Azure SQL Database on behalf of the signed-in Azure AD user
45+
46+
> [!NOTE]
47+
>Azure AD authentication is _different_ from [Integrated Windows authentication](/previous-versions/windows/it-pro/windows-server-2003/cc758557(v=ws.10)) in on-premises Active Directory (AD DS). AD DS and Azure AD use completely different authentication protocols. For more information, see [Azure AD Domain Services documentation](../active-directory-domain-services/index.yml).
48+
49+
[!INCLUDE [quickstarts-free-trial-note](../../includes/quickstarts-free-trial-note.md)]
50+
51+
## Prerequisites
52+
53+
This article continues where you left off in either one of the following tutorials:
54+
55+
- [Tutorial: Build an ASP.NET app in Azure with SQL Database](app-service-web-tutorial-dotnet-sqldatabase.md)
56+
- [Tutorial: Build an ASP.NET Core and SQL Database app in Azure App Service](tutorial-dotnetcore-sqldb-app.md).
57+
58+
If you haven't already, follow one of the two tutorials first. Alternatively, you can adapt the steps for your own .NET app with SQL Database.
59+
60+
Prepare your environment for the Azure CLI.
61+
62+
[!INCLUDE [azure-cli-prepare-your-environment-no-header.md](../../includes/cloud-shell-try-it-no-header.md)]
63+
64+
## 1. Configure database server with Azure AD authentication
65+
66+
First, enable Azure Active Directory authentication to SQL Database by assigning an Azure AD user as the admin of the server. This user is different from the Microsoft account you used to sign up for your Azure subscription. It must be a user that you created, imported, synced, or invited into Azure AD. For more information on allowed Azure AD users, see [Azure AD features and limitations in SQL Database](/azure/azure-sql/database/authentication-aad-overview#azure-ad-features-and-limitations).
67+
68+
1. If your Azure AD tenant doesn't have a user yet, create one by following the steps at [Add or delete users using Azure Active Directory](../active-directory/fundamentals/add-users-azure-active-directory.md).
69+
70+
1. Find the object ID of the Azure AD user using the [`az ad user list`](/cli/azure/ad/user#az_ad_user_list) and replace *\<user-principal-name>*. The result is saved to a variable.
71+
72+
```azurecli-interactive
73+
azureaduser=$(az ad user list --filter "userPrincipalName eq '<user-principal-name>'" --query [].id --output tsv)
74+
```
75+
76+
> [!TIP]
77+
> To see the list of all user principal names in Azure AD, run `az ad user list --query [].userPrincipalName`.
78+
>
79+
80+
1. Add this Azure AD user as an Active Directory admin using [`az sql server ad-admin create`](/cli/azure/sql/server/ad-admin#az_sql_server_ad_admin_create) command in the Cloud Shell. In the following command, replace *\<server-name>* with the server name (without the `.database.windows.net` suffix).
81+
82+
```azurecli-interactive
83+
az sql server ad-admin create --resource-group <group-name> --server-name <server-name> --display-name ADMIN --object-id $azureaduser
84+
```
85+
86+
1. Restrict the database server authentication to Active Directory authentication. This step effectively disables SQL authentication.
87+
88+
```azurecli-interactive
89+
az sql server ad-only-auth enable --resource-group <group-name> --server-name <server-name>
90+
```
91+
92+
For more information on adding an Active Directory admin, see [Provision Azure AD admin (SQL Database)](/azure/azure-sql/database/authentication-aad-configure#provision-azure-ad-admin-sql-database).
93+
94+
## 2. Enable user authentication for your app
95+
96+
You enable authentication with Azure Active Directory as the identity provider. For more information, see [Configure Azure Active Directory authentication for your App Services application](configure-authentication-provider-aad.md).
97+
98+
1. In the [Azure portal](https://portal.azure.com) menu, select **Resource groups** or search for and select *Resource groups* from any page.
99+
100+
1. In **Resource groups**, find and select your resource group, then select your app.
101+
102+
1. In your app's left menu, select **Authentication**, and then select **Add identity provider**.
103+
104+
1. In the **Add an identity provider** page, select **Microsoft** as the **Identity provider** to sign in Microsoft and Azure AD identities.
105+
106+
1. Accept the default settings and select **Add**.
107+
108+
:::image type="content" source="./media/tutorial-connect-app-access-sql-database-as-user-dotnet/add-azure-ad-provider.png" alt-text="Screenshot showing the add identity provider page." lightbox="./media/tutorial-connect-app-access-sql-database-as-user-dotnet/add-azure-ad-provider.png":::
109+
110+
> [!TIP]
111+
> If you run into errors and reconfigure your app's authentication settings, the tokens in the token store may not be regenerated from the new settings. To make sure your tokens are regenerated, you need to sign out and sign back in to your app. An easy way to do it is to use your browser in private mode, and close and reopen the browser in private mode after changing the settings in your apps.
112+
113+
## 3. Configure user impersonation to SQL Database
114+
115+
Currently, your Azure app connects to SQL Database uses SQL authentication (username and password) managed as app settings. In this step, you give the app permissions to access SQL Database on behalf of the signed-in Azure AD user.
116+
117+
1. In the **Authentication** page for the app, select your app name under **Identity provider**. This app registration was automatically generated for you. Select **API permissions** in the left menu.
118+
119+
1. Select **Add a permission**, then select **APIs my organization uses**.
120+
121+
1. Type *Azure SQL Database* in the search box and select the result.
122+
123+
1. In the **Request API permissions** page for Azure SQL Database, select **Delegated permissions** and **user_impersonation**, then select **Add permissions**.
124+
125+
:::image type="content" source="./media/tutorial-connect-app-access-sql-database-as-user-dotnet/select-permission.png" alt-text="Screenshot of the Request API permissions page showing Delegated permissions, user_impersonation, and the Add permission button selected." lightbox="./media/tutorial-connect-app-access-sql-database-as-user-dotnet/select-permission.png":::
126+
127+
## 4. Configure App Service to return a usable access token
128+
129+
The app registration in Azure Active Directory now has the required permissions to connect to SQL Database by impersonating the signed-in user. Next, you configure your App Service app to give you a usable access token.
130+
131+
In the Cloud Shell, run the following commands on the app to add the `scope` parameter to the authentication setting `identityProviders.azureActiveDirectory.login.loginParameters`.
132+
133+
```azurecli-interactive
134+
authSettings=$(az webapp auth show --resource-group <group-name> --name <app-name>)
135+
authSettings=$(echo "$authSettings" | jq '.properties' | jq '.identityProviders.azureActiveDirectory.login += {"loginParameters":["scope=openid profile email offline_access https://database.windows.net/user_impersonation"]}')
136+
az webapp auth set --resource-group <group-name> --name <app-name> --body "$authSettings"
137+
```
138+
139+
The commands effectively add a `loginParameters` property with extra custom scopes. Here's an explanation of the requested scopes:
140+
141+
- `openid`, `profile`, and `email` are requested by App Service by default already. For information, see [OpenID Connect Scopes](../active-directory/develop/v2-permissions-and-consent.md#openid-connect-scopes).
142+
- `https://database.windows.net/user_impersonation` refers to Azure SQL Database. It's the scope that gives you a JWT token that includes SQL Database as a [token audience](https://wikipedia.org/wiki/JSON_Web_Token).
143+
- [offline_access](../active-directory/develop/v2-permissions-and-consent.md#offline_access) is included here for convenience (in case you want to [refresh tokens](#what-happens-when-access-tokens-expire)).
144+
145+
> [!TIP]
146+
> To configure the required scopes using a web interface instead, see the Microsoft steps at [Refresh auth tokens](configure-authentication-oauth-tokens.md#refresh-auth-tokens).
147+
148+
Your apps are now configured. The app can now generate a token that SQL Database accepts.
149+
150+
## 5. Use the access token in your application code
151+
152+
The steps you follow for your project depends on whether you're using [Entity Framework](/ef/ef6/) (default for ASP.NET) or [Entity Framework Core](/ef/core/) (default for ASP.NET Core).
153+
154+
# [Entity Framework](#tab/ef)
155+
156+
1. In Visual Studio, open the Package Manager Console and update Entity Framework:
157+
158+
```powershell
159+
Update-Package EntityFramework
160+
```
161+
162+
1. In your DbContext object (in *Models/MyDbContext.cs*), add the following code to the default constructor.
163+
164+
```csharp
165+
var conn = (System.Data.SqlClient.SqlConnection)Database.Connection;
166+
conn.AccessToken = System.Web.HttpContext.Current.Request.Headers["X-MS-TOKEN-AAD-ACCESS-TOKEN"];
167+
```
168+
169+
# [Entity Framework Core](#tab/efcore)
170+
171+
In your `DbContext` object (in *Models/MyDbContext.cs*), change the default constructor to the following.
172+
173+
```csharp
174+
public MyDatabaseContext (DbContextOptions<MyDatabaseContext> options, IHttpContextAccessor accessor)
175+
: base(options)
176+
{
177+
var conn = Database.GetDbConnection() as SqlConnection;
178+
conn.AccessToken = accessor.HttpContext.Request.Headers["X-MS-TOKEN-AAD-ACCESS-TOKEN"];
179+
}
180+
```
181+
182+
-----
183+
184+
> [!NOTE]
185+
> The code adds the access token supplied by App Service authentication to the connection object.
186+
>
187+
> This code change doesn't work locally. For more information, see [How do I debug locally when using App Service authentication?](#how-do-i-debug-locally-when-using-app-service-authentication).
188+
189+
## 6. Publish your changes
190+
191+
# [ASP.NET](#tab/dotnet)
192+
193+
1. **If you came from [Tutorial: Build an ASP.NET app in Azure with SQL Database](app-service-web-tutorial-dotnet-sqldatabase.md)**, you set a connection string in App Service using SQL authentication, with a username and password. Use the following command to remove the connection secrets, but replace *\<group-name>*, *\<app-name>*, *\<db-server-name>*, and *\<db-name>* with yours.
194+
195+
```azurecli-interactive
196+
az webapp config connection-string set --resource-group <group-name> --name <app-name> --type SQLAzure --settings MyDbConnection="server=tcp:<db-server-name>.database.windows.net;database=<db-name>;"
197+
```
198+
199+
1. Publish your changes in Visual Studio. In the **Solution Explorer**, right-click your **DotNetAppSqlDb** project and select **Publish**.
200+
201+
:::image type="content" source="./media/app-service-web-tutorial-dotnet-sqldatabase/solution-explorer-publish.png" alt-text="Screenshot showing how to publish from the Solution Explorer in Visual Studio." lightbox="./media/app-service-web-tutorial-dotnet-sqldatabase/solution-explorer-publish.png":::
202+
203+
1. In the publish page, select **Publish**.
204+
205+
# [ASP.NET Core](#tab/dotnetcore)
206+
207+
1. **If you came from [Tutorial: Build an ASP.NET Core and SQL Database app in Azure App Service](tutorial-dotnetcore-sqldb-app.md)**, you have a connection string called `defaultConnection` in App Service using SQL authentication, with a username and password. Use the following command to remove the connection secrets, but replace *\<group-name>*, *\<app-name>*, *\<db-server-name>*, and *\<db-name>* with yours.
208+
209+
```azurecli-interactive
210+
az webapp config connection-string set --resource-group <group-name> --name <app-name> --type SQLAzure --settings defaultConnection="server=tcp:<db-server-name>.database.windows.net;database=<db-name>;"
211+
```
212+
213+
1. You would have made your code changes in your GitHub fork, with Visual Studio Code in the browser. From the left menu, select **Source Control**.
214+
215+
1. Type in a commit message like `OBO connect` and select **Commit**.
216+
217+
The commit triggers a GitHub Actions deployment to App Service. Wait a few minutes for the deployment to finish.
218+
219+
-----
220+
221+
When the new webpage shows your to-do list, your app is connecting to the database on behalf of the signed-in Azure AD user.
222+
223+
![Azure app after Code First Migration](./media/app-service-web-tutorial-dotnet-sqldatabase/this-one-is-done.png)
224+
225+
You should now be able to edit the to-do list as before.
226+
227+
## 7. Clean up resources
228+
229+
In the preceding steps, you created Azure resources in a resource group. If you don't expect to need these resources in the future, delete the resource group by running the following command in the Cloud Shell:
230+
231+
```azurecli-interactive
232+
az group delete --name <group-name>
233+
```
234+
235+
This command may take a minute to run.
236+
237+
## Frequently asked questions
238+
239+
- [Why do I get a `Login failed for user '<token-identified principal>'.` error?](#why-do-i-get-a-login-failed-for-user-token-identified-principal-error)
240+
- [How do I add other Azure AD users or groups in Azure SQL Database?](#how-do-i-add-other-azure-ad-users-or-groups-in-azure-sql-database)
241+
- [How do I debug locally when using App Service authentication?](#how-do-i-debug-locally-when-using-app-service-authentication)
242+
- [What happens when access tokens expire?](#what-happens-when-access-tokens-expire)
243+
244+
#### Why do I get a `Login failed for user '<token-identified principal>'.` error?
245+
246+
The most common causes of this error are:
247+
248+
- You're running the code locally, and there's no valid token in the `X-MS-TOKEN-AAD-ACCESS-TOKEN` request header. See [How do I debug locally when using App Service authentication?](#how-do-i-debug-locally-when-using-app-service-authentication).
249+
- Azure AD authentication isn't configured on your SQL Database.
250+
- The signed-in user isn't permitted to connect to the database. See [How do I add other Azure AD users or groups in Azure SQL Database?](#how-do-i-add-other-azure-ad-users-or-groups-in-azure-sql-database).
251+
252+
#### How do I add other Azure AD users or groups in Azure SQL Database?
253+
254+
1. Connect to your database server, such as with [sqlcmd](/azure/azure-sql/database/authentication-aad-configure#sqlcmd) or [SSMS](/azure/azure-sql/database/authentication-aad-configure#connect-to-the-database-using-ssms-or-ssdt).
255+
1. [Create contained users mapped to Azure AD identities](/azure/azure-sql/database/authentication-aad-configure#create-contained-users-mapped-to-azure-ad-identities) in SQL Database documentation.
256+
257+
The following Transact-SQL example adds an Azure AD identity to SQL Server and gives it some database roles:
258+
259+
```sql
260+
CREATE USER [<user-or-group-name>] FROM EXTERNAL PROVIDER;
261+
ALTER ROLE db_datareader ADD MEMBER [<user-or-group-name>];
262+
ALTER ROLE db_datawriter ADD MEMBER [<user-or-group-name>];
263+
ALTER ROLE db_ddladmin ADD MEMBER [<user-or-group-name>];
264+
GO
265+
```
266+
267+
#### How do I debug locally when using App Service authentication?
268+
269+
Because App Service authentication is a feature in Azure, it's not possible for the same code to work in your local environment. Unlike the app running in Azure, your local code doesn't benefit from the authentication middleware from App Service. You have a few alternatives:
270+
271+
- Connect to SQL Database from your local environment with [`Active Directory Interactive`](/sql/connect/ado-net/sql/azure-active-directory-authentication#using-active-directory-interactive-authentication). The authentication flow doesn't sign in the user to the app itself, but it does connect to the back-end database with the signed-in user, and allows you to test database authorization locally.
272+
- Manually copy the access token from `https://<app-name>.azurewebsites.net/.auth/me` into your code, in place of the `X-MS-TOKEN-AAD-ACCESS-TOKEN` request header.
273+
- If you deploy from Visual Studio, use remote debugging of your App Service app.
274+
275+
#### What happens when access tokens expire?
276+
277+
Your access token expires after some time. For information on how to refresh your access tokens without requiring users to reauthenticate with your app, see [Refresh identity provider tokens](configure-authentication-oauth-tokens.md#refresh-auth-tokens).
278+
279+
## Next steps
280+
281+
What you learned:
282+
283+
> [!div class="checklist"]
284+
> * Enable built-in authentication for Azure SQL Database
285+
> * Disable other authentication options in Azure SQL Database
286+
> * Enable App Service authentication
287+
> * Use Azure Active Directory as the identity provider
288+
> * Access Azure SQL Database on behalf of the signed-in Azure AD user
289+
290+
> [!div class="nextstepaction"]
291+
> [Map an existing custom DNS name to Azure App Service](app-service-web-tutorial-custom-domain.md)
292+
293+
> [!div class="nextstepaction"]
294+
> [Tutorial: Access Microsoft Graph from a secured .NET app as the app](scenario-secure-app-access-microsoft-graph-as-app.md)
295+
296+
> [!div class="nextstepaction"]
297+
> [Tutorial: Isolate back-end communication with Virtual Network integration](tutorial-networking-isolate-vnet.md)

0 commit comments

Comments
 (0)