Azure SQL: contained user for managed identity is created from the principal (object) id, but SQL matches on client id
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 991
- Avg merge
- 2d 15h
- Merged PRs (30d)
- 196
Description
## Summary
The generated `api-roles-` module creates the contained SQL user from the managed identity's **principal (object) id**. Azure SQL matches managed identities on their **client (application) id**, so the resulting user does not match the token and the application fails to log in.
## Details
`api-identity.module.bicep` exposes both:
```bicep
output clientId string = api_identity.properties.clientId
output principalId string = api_identity.properties.principalId
```
and `main.bicep` passes the principal id to the roles module:
```bicep
module api_roles_control_sql 'api-roles-control-sql/api-roles-control-sql.module.bicep' = {
params: {
principalId: api_identity.outputs.principalId
principalName: api_identity.outputs.principalName
}
}
```
The script converts that GUID to a SID:
```sql
DECLARE @id UNIQUEIDENTIFIER = '$principalId';
DECLARE @castId NVARCHAR(MAX) = CONVERT(VARCHAR(MAX), CONVERT (VARBINARY(16), @id), 1);
DECLARE @cmd NVARCHAR(MAX) = N'CREATE USER [' + @name + '] WITH SID = ' + @castId + ', TYPE = E;'
```
## Observed behaviour
I hit #19389 first, so I created the contained user by hand using the same statement and the same `principalId`. The application then failed with:
```
Microsoft.Data.SqlClient.SqlException (0x80131904): Login failed for user ''.
```
Recreating the user with `CREATE USER [] FROM EXTERNAL PROVIDER`, which lets SQL resolve the SID from Entra, produced a **different** SID — the little-endian encoding of the identity's **client id**, not its object id. With that user in place the application connects successfully.
Concretely, for a user-assigned identity with:
- `clientId` = `c5055d7d-d66d-468d-8984-077214496243`
- `principalId` = `ad966088-94ed-440e-87c7-ecd56daac796`
`FROM EXTERNAL PROVIDER` yields SID `0x7D5D05C56DD68D468984077214496243`, which is the client id. The script's `principalId`-derived SID does not match what the token presents.
## Impact
Once #19389 is fixed, the script will run to completion but still create a user that cannot authenticate, so this is likely masked by that bug today.
## Suggested fix
Pass `api_identity.outputs.clientId` to the roles module for the SID, or use `CREATE USER [name] FROM EXTERNAL PROVIDER` and let SQL resolve the identity by name.
## Environment
- Aspire 13.4.6 (same script emitted by 13.3.5), azd 1.25.0, .NET 10.0.300
- Azure SQL serverless `GP_S_Gen5`, Entra-only authentication, user-assigned managed identity on Azure App Service Linux
Contributor guide
Research direction
Start by tracing the identity outputs in api-identity.module.bicep through the parameters in main.bicep into api-roles-control-sql/api-roles-control-sql.module.bicep and its generated SQL. Verify that the contained user SID corresponds to the managed identity client ID and that the deployed application can authenticate successfully with the resulting user.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, sql
- Domain
- cloud, databases
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100