question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[QUERY] Token is expired using Identity with SQL

See original GitHub issue

Library name and version

Azure.Identity 1.8

Query/Question

I am trying to update an Azure app service to use User-assigned managed identity to access SQL but am running into the following error: The underlying provider failed on Open. Login failed for user '<token-identified principal>'. Token is expired. Appreciate any ideas on what should be done differently to solve this issue?

Details: I have a Class Library targeting .Net Framework 4.7, using Entity Framework 6.4 to connect to Azure SQL (database first). Class library references System.Data.SqlClient, Azure.Core 1.28 and Azure.Identity 1.8.

I have modified the DbContext class to use a supported connection string (see below) and use a static DefaultAzureCredential class to fetch a token from Azure AD and assign it to the connection object. When a Web API application using the class library is deployed to Azure App service, everything works as expected for about 24 hours, then the following errors starts popping up:

**The underlying provider failed on Open. 
Login failed for user '<token-identified principal>'. Token is expired.**

These errors go away if the app is restarted and it works fine for another 24 hours before needing a restart. Azure AD managed identity tokens have a lifetime of about 24 hours. It looks like when the token expires, the connections in the pool retain the old token and fail upon subsequent use. Should the SDK handle refreshing the tokens for the connections in the pool?

I have also tried the following without success:

  1. Declare DefaultAzureCredential as a non-static class member - Causes every request to get a new instance of DefaultAzureCredential. Seeing degraded performance in the database calls, because DefaultAzureCredential attempts to use all the possible different ways to login until it finds one that works.
  2. Declare DefaultAzureCredential as non-static but made it a scoped instance, per request - same issue as in number 1.
  3. Declare AccessToken as a static class member and handle token refresh based on the expiry - Same error as original. SQL Connections retain the expired token which leads to login failed errors.
  4. Automatically retry any commands that fail due to connection breaks by using Entity Framework’s SqlAzureExecutionStrategy (SQL Error: 18456) - Retries do happen but without first fetching a new token from Azure AD.

Here is what the code in the class library looks like:

public partial class TestDbEntities
{
	private static volatile DefaultAzureCredential _defaultAzureCredential = null;
	private static object _syncObject = new object();
	private static readonly Regex connectionRegex = new Regex(@"provider\s*connection\s*string\s*=\s*""(?<conn>[^""]+)",
															   RegexOptions.Compiled | RegexOptions.IgnoreCase);


	public TestDbEntities()
		: base("name=TestDbEntities")
	{
		DefaultAzureCredential defaultCredentialInstance = GetDefaultCredentialInstance();

		var conn = (SqlConnection)Database.Connection;
		var connectionString = <<get connection string from config>>
		conn.ConnectionString = connectionRegex.Match(connectionString).Groups["conn"].ToString();

		AccessToken accessToken = defaultCredentialInstance.GetToken(new TokenRequestContext(new[] { "https://database.windows.net/.default" }));
		conn.AccessToken = accessToken.Token;
	}



	private DefaultAzureCredential GetDefaultCredentialInstance()
	{
		if (_defaultAzureCredential == null)
		{
			lock (_syncObject)
			{
				if (_defaultAzureCredential is null)
				{
					DefaultAzureCredentialOptions defaultAzureCredentialOptions = new DefaultAzureCredentialOptions
					{
						ExcludeAzureCliCredential = true,
						ExcludeAzurePowerShellCredential = true,
						ExcludeEnvironmentCredential = true,
						ExcludeSharedTokenCacheCredential = true,
						ExcludeInteractiveBrowserCredential = true,
						ExcludeManagedIdentityCredential = false,
						ExcludeVisualStudioCodeCredential = true,
						ExcludeVisualStudioCredential = true,
						ManagedIdentityClientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID")
					};

					_defaultAzureCredential = new DefaultAzureCredential(defaultAzureCredentialOptions);
				}
			}
		}
		return _defaultAzureCredential;
	}
}

EF connection string looks like:

<add name="TestDbEntities" connectionString="metadata=res://*/Entity.TestDbModel.csdl|res://*/Entity.TestDbModel.ssdl|res://*/Entity.TestDbModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;server=tcp:xxxx.database.windows.net,1433;database=Test;persist security info=True;MultipleActiveResultSets=True;App=TestMI;&quot;" providerName="System.Data.EntityClient" />

Environment

No response

Issue Analytics

  • State:closed
  • Created 5 months ago
  • Comments:11 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
David-Engelcommented, Apr 28, 2023

Yes, Mirosoft.Data.SqlClient (MDS) has a lot of improvements around Azure AD auth and token handling. It even has authentication=Active Directory Default, which uses DefaultAzureCredential underneath, so you don’t have to do your own token acquisition. However, even in MDS, just setting the AccessToken property still has limitations since it doesn’t include expiration info. We are considering additional token authentication options in the future.

If you aren’t ready to move to MDS, you can override the implementation of the “Active Directory Interactive” authentication provider with your own class that implements “SqlAuthenticationProvider” and register it in your application by calling the “SetProvider” API. With that method, SDS knows the expiration date and should recognize that a pooled connection has an expired token and remove it from the pool. Custom providers are still supported in

Code sample (MDS-specific, but still applies to SDS if you pick an authentication method from SDS): https://github.com/dotnet/SqlClient/blob/main/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs

0reactions
psinha17commented, Apr 28, 2023

Thanks @David-Engel for providing additional context.

DefaultAzureCredential is responsible for getting a new token that typically expires 24 hours after the initial request. However, it does not get a new token until about 3-5 minutes before expiry (the SDK returns the same token until the previous one is about to expire).

Because new connections can be created at any time, Connection Lifetime would have to be dynamic depending on how much time was left before expiry. This will cause numerous pools to be created (one per distinct connection string). I can check how it performs.

Would have been ideal if the SqlConnection had information on the expiration date of the token. A pool could use that information to not return a connection with an expired token.

Looks like Microsoft.Data.SqlClient solves for this issue. I see a PR #635 for the same.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Login failed for user '<token-identified principal ...
I am using a function app which is service bus triggered. It connects to Azure SQL Server using Managed Identity . The connection...
Read more >
Authentication token has expired. The user must ...
A session token is generated using the Master Token and is refreshed every 60 minutes (3600s) as new queries are requested.
Read more >
Azure app service to sql database over system managed ...
Is any way how to validate way access token generated by Azure app service, not expired with system managed identity was not accepted...
Read more >
Efficient Cleaning Up of the Persisted Grant Table
IdentityServer uses a persisted grants table to store reference and refresh tokens. Clean-up code needs to be run periodically to remove expired tokens....
Read more >
Authentication for Databricks automation
To perform Databricks personal access token authentication, integrate the following within your code, based on the participating tool or SDK:.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found