DBMigrator fails to read accesstoken in ef6 codefirst - SQL Connection - c#

I have a WebAPI with ef6 code first setup and working. In app start we have dbmigrator.Update() which applies any pending migrations to database.
After changing the connection string to remove username and password and provide access token instead, dbmigrator.Update() fails with an error:
Login failed for user ''
How to ensure that dbmigrator works with Azure SQL access token instead of username/password in connection string?
Edit 1:
The change done to dbcontext constructor is to change it from
DbContext() : base("nameofConnString"){}
to
DbContext() : base(GetSQLConn(), true)
{
Database.SetInitializer<DbContext>(null);
}
With GetSQLConn(), I am retrieving a connection without uname/pwd and attaching accesstoken to it and returning the connection!
Edit 2:
private static SqlConnection GetSQLConn()
{
var accessToken = TokenFactory.AcquireToken();
var connString = ConfigurationManager.ConnectionStrings["SQLConnectionString"].ConnectionString;
var conn = new SqlConnection(connString)
{
AccessToken = accessToken,
};
return conn;
}

How to ensure that dbmigrator works with Azure SQL access token instead of username/password in connection string?
actually dbcontext works for CRUD operations on all my tables, just this migrator won't work!
According to your comment, it seems that you have no permission to alert table. If you don't grant corresponding permission for the created user. Please have a try to grant permission to the created user. More details about db role please refer to the Database-Level Roles.
EXEC sp_addrolemember N'db_owner', N'your-user-name'
I also following the SO thread you mentioned.If I add the user to do_owner, I test it with Alter Table, it works correctly on my side. The following is my detail steps.
1.Registry an Azure AD Application
2.Provision an Azure Active Directory administrator for your Azure SQL server, more details please refer to the this tutorials
3.Create user for the Azure SQL and grant corresponding permission.
CREATE USER [RegistryAppName] FROM EXTERNAL PROVIDER
EXEC sp_addrolemember N'db_owner', 'RegistryAppName'
4.Change the demo code and run it as expected.
SqlConnectionStringBuilder builder =
new SqlConnectionStringBuilder
{
["Data Source"] = "azureServername.database.windows.net",
["Initial Catalog"] = "databaseName",
["Connect Timeout"] = 30
};
// replace with your server name
// replace with your database name
string accessToken = TokenFactory.GetAccessToken();
if (accessToken == null)
{
Console.WriteLine("Fail to acuire the token to the database.");
}
using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
{
try
{
connection.AccessToken = accessToken;
connection.Open();
var commentText = "ALTER TABLE AccountRec ADD newColumn varchar(10) ";
SqlCommand sqlCommand = new SqlCommand(commentText, connection);
Console.WriteLine("Executed Result:" + sqlCommand.ExecuteNonQuery());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Console.WriteLine("Please press any key to stop");
Console.ReadKey();

Related

Azure SQL Access Token Error - The server is not currently configured to accept this token

I am using the following code to retrieve an access token so that my application can access an Azure SQL database:
public static SqlConnection CreateAzureConnection(IConfiguration _configuration, CancellationToken cancellationToken)
{
var objDefaultAzureCredentialOptions = new DefaultAzureCredentialOptions
{
ExcludeEnvironmentCredential = false,
ExcludeManagedIdentityCredential = true,
ExcludeSharedTokenCacheCredential = true,
ExcludeVisualStudioCredential = false,
ExcludeVisualStudioCodeCredential = true,
ExcludeAzureCliCredential = true,
ExcludeInteractiveBrowserCredential = true
};
var credential = new DefaultAzureCredential(objDefaultAzureCredentialOptions);
var token = credential.GetToken(new TokenRequestContext(new[] { "https://database.windows.net/.default" }), cancellationToken);
SqlConnection conn = new(_configuration.GetConnectionString("Sales"))
{
AccessToken = token.Token
};
return conn;
}
This seems fine and I get a token back but when I attempt to connect and run a query I get the following error:
One or more errors occurred. (Login failed for user ''. The server is not currently configured to accept this token.)
I have tried changing the options for what credentials to exclude. The app will be deployed onto an approved environment, but my local dev area is not approved so I have added the VS credentials option. I've confirmed that my credentials in Tools > Options > Azure Service Authentication are correct.
When I use only environment credentials it fails to get the token at all, so I believe everything about the token itself is fine. The error mentions the server not being configured for this token type, so i'm guessing there's some setup that's needed on the Azure SQL server itself, but i've been unable to find what so far
I have created webapp in visual studio and connected to Azure SQL database using below process:
I have given connection string in appsetting.json file
as
"ConnectionStrings": {
"QuotesDatabase": "Server=tcp:<servename>.database.windows.net,1433; Database=<databasename>;" }
Image for reference:
I added required packages to connect database.
Packages:
I used below code to connect Azure SQL database:
var connectionString = Configuration.GetConnectionString("<connectionstringname>");
services.AddTransient(a =>{
var sqlConnection = new SqlConnection(connectionString);
var credential = new DefaultAzureCredential();
var token = credential
.GetToken(new Azure.Core.TokenRequestContext(
new[] { "https://database.windows.net/.default" }));
sqlConnection.AccessToken = token.Token;
return sqlConnection;
Image for reference:
I used below query to retrieve data from SQL database:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlClient;
using System.Threading.Tasks;
namespace SqlMSI.Controllers
{
[ApiController]
[Route("[controller]")]
public class QuotesController : ControllerBase
{
private readonly string connectionString;
public QuotesController(SqlConnection dbconnection)
{
DbConnection = dbconnection;
}
public SqlConnection DbConnection { get; }
public async Task<IActionResult> Get()
{
DbConnection.Open();
var sqlCommand = new SqlCommand("select * from quote", DbConnection);
var reader = sqlCommand.ExecuteReader();
var quotes = new List<Quote>();
while (reader.Read())
{
var quote = new Quote()
{
Id = Guid.Parse(reader["Id"].ToString()),
QuoteNumber = int.Parse(reader["QuoteNumber"].ToString())
};
quotes.Add(quote);
}
return Ok(quotes);
}
}
public class Quote
{
public Guid Id { get; set; }
public int QuoteNumber { get; set; }
}
}
Image for reference:
I set myself as azure service authentication to retrieve the token credentials.
Image for reference:
I set myself as admin to the SQL server in Azure portal.
Image for reference:
I added webapp IP address to the SQL server
Image for reference:
It run successfully and connected to azure SQL database and retrieve the data from database without any error.
Image for reference:
It worked in my machine please recheck from your end.

Convert Entity Framework DB Connection to use Azure Identity using Powershell Credentials

We have an application that is setup to use EntityFramework. I need to update that application to access the database using Azure Identity. Basically I need to do the following:
Get the access token using AzurePowershell Credentials
Create the SQL Connection using that access token
Add the token to the connection and return it to the caller
Here's what I have so far (but I know i'm missing alot of things here coz I am getting compilation errors). I was looking for some examples to do this but wasn't successful.
public static DbConnection CreateConnection( string efConnectionString )
{
var credential = new AzurePowerShellCredential();
System.Threading.CancellationToken cancellationToken = default;
TokenRequestContext requestContext = "https://database.windows.net/.default";
string accessToken = AzurePowerShellCredential.GetToken( "https://database.windows.net/.default", cancellationToken );
SqlConnectionStringBuilder sqlConnection = new SqlConnectionStringBuilder( efConnectionString );
//create sql connection
using( SqlConnection sqlConn = new SqlConnection( efConnectionString ) )
{
sqlConn.AccessToken = accessToken;
return sqlConn;
}
For starters the requestContext doesn't like being set up as a string and I'm trying to figure out the best way to add the token to the connection string and send it back successfully. Also, the AzurePowerShellCredential.GetToken doesn't like the string that I am passing with the database address.
My Connection string that is coming in looks like this:
"Server=tcp:servername.database.windows.net,1433;Database=databasename;User ID=UserID#servername;Password=password;Trusted_Connection=False;Encrypt=True;"
In order for this to work successfully, I will be ommitting the userid and password from the above connections string so it can be replaced by the AzureCredentials.
I really appreciate any help that can be provided. Thanks!
Your code is correct. The only thing I would suggest is to use DefaultAzureCredential which would allow you to use different authentication flows for the database.
As for a connection string it's format can be following:
using Azure.Core;
using Azure.Identity;
using System.Data.SqlClient;
var connString = "Server=tcp:<your-server-name>.database.windows.net,1433;Database=<database-name>;";
var credential = new DefaultAzureCredential();
var token = credential
.GetToken(
new TokenRequestContext(scopes: new[] { "https://database.windows.net/.default" })
);
using var conn = new SqlConnection(connString);
conn.AccessToken = token.Token;
conn.Open();
One more thing you should think about is access token renewal. EntityFramework caches database connections so the token might expire while the connection is alive. As an option I can suggest you to use existing solution like in this example if you have an ASP.NET app or try to reverse-engineer token update logic like in Microsoft.Data.SqlClient (example)

How to connect an already existing .Net Core application to Azure SQL Database with managed identity

What are the steps to successfully connect the application to Azure SQL Database after setting up the connection string and adding the App Authentication NuGet package.
If you want to use Azure Managed Identity to connect Azure SQL database in .Net Core MVC project, We can use the package Microsoft.Data.SqlClient with SqlConnection.AccessToken.
The detailed steps are as below.
Create MSI
Configure SQL Database
a. Use your Azure Sql AD admin to connect Azure SQL vai SSMS
b. Add the MSI to the database you need use
USE [<db name>]
GO
create user [<your msi name>] from external provider
ALTER ROLE db_owner ADD MEMBER [<function app name>]
Code
/*
Install SDK Microsoft.Azure.Services.AppAuthentication and Microsoft.Data.SqlClient
*/
public async Task<IActionResult> Index()
{
List<StarWar> starWars = new List<StarWar>();
var connectionString = "Server=tcp:<server-name>.database.windows.net,1433;Database=<database-name>;";
using (var conn = new SqlConnection(connectionString))
{
conn.AccessToken = await (new Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider()).GetAccessTokenAsync("https://database.windows.net/");
await conn.OpenAsync();
var sql = "SELECT * FROM [dbo].[StarWars]";
using (SqlCommand command = new SqlCommand(sql, conn))
{
using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
StarWar starWar = new StarWar();
starWar.episode = Convert.ToInt32(reader["episode"]);
starWar.score = Convert.ToInt32(reader["score"]);
starWar.name = Convert.ToString(reader["name"]);
starWars.Add(starWar);
}
}
}
}
return View(starWars);
}

Connecting to Azure DB with Active Directory-Universal with MFA Support Authentication in C#

I need to access some part of SQL table data from c# console application.I need help to establish the server connection from c#.
DataBase Details:
Server type : Database Engine
Authentication : Active Directory-Universal with MFA support.
Also please do let me know How should I give my Connection properties?
If you don't want to fiddle with tokens or register your C# app as an Azure Application, you can use ODBC or OLE DB with the MSOLEDBSQL driver, which is able to use MFA / ActiveDirectoryInteractive authentication out of the box:
ODBC:
using System.Data.Odbc;
...
OdbcConnection con = new OdbcConnection("Driver={ODBC Driver 17 for SQL Server};SERVER=sqlserver.database.windows.net;DATABASE=database;Authentication=ActiveDirectoryInteractive;UID=user#domain.com");
OLE DB:
using System.Data.OleDb;
...
OleDbConnection con = new OleDbConnection("Provider=MSOLEDBSQL;Data Source=sqlserver.database.windows.net;User ID=user#domain.com;Initial Catalog=database;Authentication=ActiveDirectoryInteractive");
There are 2 different scenarios for logging in to the database.
1) The user logs in with their account and you use that token to authenticate to the SQL database. In this case you can use the standard login popups which will handle the MFA piece for you.
2) Either the user doesn't have privileges on the DB (most standard web apps are an example of this) or you are creating an automated service that needs to log in to the DB. Since the reason for MFA is to have the user complete some action that a machine can't, like entering a code from their cell phone, unattended logins don't work with MFA.
If you are in the second scenario, you will need to create a service principle account that is not protected by MFA for the app to login. Instead of a user name and password, the app gets a unique appId and appSecret to use to access the database. You can add additional protection by placing the secret in Key Vault and limiting the access of that app to just the specific resources it needs to function.
Notice that in this case we aren't passing a user name and password in with the connection string. Instead we get the token separately before adding it to the connection.
string serverName = "myserver.database.windows.net";
string databaseName = "test";
string clientId = "xxxxxx-xxxxx-xxxxx-xxxx-xxxx";
string aadTenantId = "xxxxxx-xxxxxx-xxxxxx-xxxxxx-xxxxxxxx";
string clientSecretKey = "xxxxx/xxxxxx/xxxxx";
string sqlConnectionString = String.Format("Data Source=tcp:{0},1433;Initial Catalog={1};Persist Security Info=False;Connect Timeout=30;Encrypt=True;TrustServerCertificate=False", serverName, databaseName);
string AadInstance = "https://login.windows.net/{0}";
string ResourceId = "https://database.windows.net/";
AuthenticationContext authenticationContext = new AuthenticationContext(string.Format(AadInstance, aadTenantId));
ClientCredential clientCredential = new ClientCredential(clientId, clientSecretKey);
DateTime startTime = DateTime.Now;
Console.WriteLine("Time " + String.Format("{0:mm:ss.fff}", startTime));
AuthenticationResult authenticationResult = authenticationContext.AcquireTokenAsync(ResourceId, clientCredential).Result;
DateTime endTime = DateTime.Now;
Console.WriteLine("Got token at " + String.Format("{0:mm:ss.fff}", endTime));
Console.WriteLine("Total time to get token in milliseconds " + (endTime - startTime).TotalMilliseconds);
using (var conn = new SqlConnection(sqlConnectionString))
{
conn.AccessToken = authenticationResult.AccessToken;
//DO STUFF
}

Add data to Identity default database using connectionstring

I am currently working on a learning project where I used to use forms auth with an sql database. But today I have updated to using Indetity.
The problem I have now is that my site stores more than user information, and I want that in the same database as the Identity data.
When I used membership this was no problem, I just added a connectionstring and wrote an SQL statement. But now it seems I need to add something called DbContext?
Maybe it's easier to look at my code to understand, This is my old code, used for old SQL database:
using (SqlConnection con = new SqlConnection(strCon))
{
using (SqlCommand cmd = con.CreateCommand())
{
cmd.CommandText = "INSERT INTO Website (url, rating, categoryId, subCategoryId, description1, description2) values (#url, #rating, #categoryId, #subCategoryId, #desc1, #desc2)";
cmd.Parameters.AddWithValue("#url", url);
cmd.Parameters.AddWithValue("#rating", rating);
cmd.Parameters.AddWithValue("#categoryId", categoryId);
cmd.Parameters.AddWithValue("#desc1", desc1);
cmd.Parameters.AddWithValue("#desc2", desc2);
if (DPLSubCategory.Items != null)
{
Int32 subCategoryId = Convert.ToInt32(DPLSubCategory.SelectedItem.Value);
cmd.Parameters.AddWithValue("#subCategoryId", subCategoryId);
}
con.Open();
cmd.ExecuteNonQuery();
}
con.Close();
}
Now say I add a "Website" table to my local Identity database. How can I do something similar to the code above? Right now I am using default connectionstring:
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\WDBAPP.mdf;Initial Catalog=WDBAPP;Integrated Security=True" providerName="System.Data.SqlClient"/>
And I add members like this:
// Default UserStore constructor uses the default connection string named: DefaultConnection
var userStore = new UserStore<IdentityUser>();
var manager = new UserManager<IdentityUser>(userStore);
var user = new IdentityUser() { UserName = UserName.Text };
IdentityResult result = manager.Create(user, Password.Text);
if (result.Succeeded)
{
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
var userIdentity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties() { }, userIdentity);
Response.Redirect("~/Login.aspx");
}
As you can see I have no need to point to my database since I use the default connectionstring.
So how do I change my connectionstring to, for example, "MyConString" and still have it work with the registration code? I want to be able to point to my database from my code so that I can add whatever I want.
Web forms do not have a applicationdbcontext class that I can find
and modify, and I do not know how to create one.
Default Web Form Template in VS 2013 has ApplicationDbContext.
If you are new to ASP.Net Identity, you want to use Default Template created by VS first before hand-rolling your own UserManager, because a lot of things can go wrong easily.
If you want to keep ASP.Net Identity inside your existing database, you need to rename it same as your existing connection string.

Categories

Resources