Web API 2.0 Using MySQL Identity Provider, AuthContext? - c#

I am attempting to setup token authentication using ASP.NET Identity in a WebAPI 2 with a MySQL Backend using this tutorial.
http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
Unfortunately, this example uses Entity Framework and I am attempting to substitute it where ever I can (if it is even possible) with MySQL.
I have so far come to step 5, creating an authentication repository. This is the example given:
public class AuthRepository : IDisposable
{
private AuthContext _ctx;
private UserManager<IdentityUser> _userManager;
public AuthRepository()
{
_ctx = new AuthContext();
_userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(_ctx));
}
public async Task<IdentityResult> RegisterUser(UserModel userModel)
{
IdentityUser user = new IdentityUser
{
UserName = userModel.UserName
};
var result = await _userManager.CreateAsync(user, userModel.Password);
return result;
}
public async Task<IdentityUser> FindUser(string userName, string password)
{
IdentityUser user = await _userManager.FindAsync(userName, password);
return user;
}
public void Dispose()
{
_ctx.Dispose();
_userManager.Dispose();
}
}
I substituted UserManager for MySQLUserManager, but I cannot make an AuthContext.
Does anybody know what I must reference, or replace.
Maybe I do not even need to reference it, as in my IdentityConfig.cs file I only have:
//Old entity framework stuff
//var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
//shiny new mysql stuff
var manager = new ApplicationUserManager(new MySqlUserStore<ApplicationUser>());
(based off this tutorial http://blog.developers.ba/asp-net-identity-2-1-for-mysql/)
If I need an application user, should I pass it somehow?
I re-read the tutorial and came across this snippet, it appears to be a connection string of some sort.
<connectionStrings>
<add name="AuthContext" connectionString="Data Source=.\sqlexpress;Initial Catalog=AngularJSAuth;Integrated Security=SSPI;" providerName="System.Data.SqlClient" />
</connectionStrings>
What could or should I replace it with?

Related

EF Core DB First, and how to avoid Constructor Overwrite on Model Generation

I am moving away from using SQL Authentication with my Azure DB, to Active Directory Managed Authentication as explained in this article.
Basically, I'm doing two main things to get this working.
1- injecting the token in the DBContext constructor:
public MyDBContext(DbContextOptions<MyDBContext> options)
: base(options)
{
var conn = (SqlConnection)Database.GetDbConnection();
conn.AccessToken = (new Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider()).GetAccessTokenAsync("https://database.windows.net/").Result;
}
2- In My Web App Startup file, I'm injecting the DBContext
string SqlConnection = localConfig["SqlConnectionString"];
services.AddDbContext<MyDBContext>(options => options.UseSqlServer(SqlConnection, sqlServerOptions => { sqlServerOptions.CommandTimeout(1000); }));
My problem now is that every time I need to refresh the model using the Scaffold-DbContext command, my MyDbContext gets overwritten, and I lose the changes I've done to the constructor.
What solutions are possible to avoid this problem? OR, is there a better way to inject the Token somewhere else efficiently?
Edit:
Please note that I am using EF 2.x
I've used an interceptor to inject access tokens:
public class ManagedIdentityConnectionInterceptor : DbConnectionInterceptor
{
private readonly bool _useManagedIdentity;
private readonly AzureServiceTokenProvider _tokenProvider;
public ManagedIdentityConnectionInterceptor(bool useManagedIdentity)
{
_useManagedIdentity = useManagedIdentity;
_tokenProvider = new AzureServiceTokenProvider();
}
public override async Task<InterceptionResult> ConnectionOpeningAsync(
DbConnection connection,
ConnectionEventData eventData,
InterceptionResult result,
CancellationToken cancellationToken = default)
{
if (_useManagedIdentity)
{
// In Azure, get an access token for the connection
var sqlConnection = (SqlConnection)connection;
var accessToken = await GetAccessTokenAsync(cancellationToken);
sqlConnection.AccessToken = accessToken;
}
return result;
}
private async Task<string> GetAccessTokenAsync(CancellationToken cancellationToken)
{
// Get access token for Azure SQL DB
var resource = "https://database.windows.net/";
return await _tokenProvider.GetAccessTokenAsync(resource, cancellationToken: cancellationToken);
}
}
Which can be added like this:
// Detect environment somehow (locally you might not want to use tokens)
var useManagedIdentity = true;
var managedIdentityInterceptor = new ManagedIdentityConnectionInterceptor(useManagedIdentity);
services.AddDbContext<Context>(options => options.UseSqlServer(connectionString).AddInterceptors(managedIdentityInterceptor));
This way no changes are needed in the constructor.
The interceptor will get a token before a connection is opened to the SQL DB.
Also we avoid doing sync-over-async in the constructor.
Do note this requires EF Core 3.x.
You can use the UseSqlServer overload with DbConnection parameter and pass configured SqlConnection object:
var sqlConnectionString = localConfig["SqlConnectionString"];
services.AddDbContext<MyDBContext>(
options => options.UseSqlServer(new SqlConnection(sqlConnectionString)
{
AccessToken = (new Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider()).GetAccessTokenAsync("https://database.windows.net/").Result
},
sqlServerOptions => { sqlServerOptions.CommandTimeout(1000); }));

DB-First authentication confusion with ASP.NET Web API 2 + EF6

I need to create a Web API C# application for an existing MySQL database. I've managed to use Entity Framework 6 to bind every database table to a RESTful API (that allows CRUD operations).
I want to implement a login/registration system (so that I can implement roles and permissions in the future, and restrict certain API requests).
The MySQL database I have to use has a table for users (called user) that has the following self-explanatory columns:
id
email
username
password_hash
It seems that the de-facto standard for authentication is ASP.Net Identity. I have spent the last hour trying to figure out how to make Identity work with an existing DB-First Entity Framework setup.
If I try to construct ApplicationUser instances storing user instances (entities from the MySQL database) to retrieve user data, I get the following error:
The entity type ApplicationUser is not part of the model for the current context.
I assume I need to store Identity data in my MySQL database, but couldn't find any resource on how to do that. I've tried completely removing the ApplicationUser class and making my user entity class derive from IdentityUser, but calling UserManager.CreateAsync resulted in LINQ to Entities conversion errors.
How do I setup authentication in a Web API 2 application, having an existing user entity?
You say:
I want to implement a login/registration system (so that I can
implement roles and permissions in the future, and restrict certain
API requests).
How do I setup authentication in a Web API 2 application, having an
existing user entity?
It definitely means that you DO NOT need ASP.NET Identity. ASP.NET Identity is a technology to handle all users stuffs. It actually does not "make" the authentication mechanism. ASP.NET Identity uses OWIN Authentication mechanism, which is another thing.
What you are looking for is not "how to use ASP.NET Identity with my existing Users table", but "How to configure OWIN Authentication using my existing Users table"
To use OWIN Auth follow these steps:
Install the packages:
Owin
Microsoft.AspNet.Cors
Microsoft.AspNet.WebApi.Client
Microsoft.AspNet.WebApi.Core
Microsoft.AspNet.WebApi.Owin
Microsoft.AspNet.WebApi.WebHost
Microsoft.Owin
Microsoft.Owin.Cors
Microsoft.Owin.Host.SystemWeb
Microsoft.Owin.Security
Microsoft.Owin.Security.OAuth
Create Startup.cs file inside the root folder (example):
make sure that [assembly: OwinStartup] is correctly configured
[assembly: OwinStartup(typeof(YourProject.Startup))]
namespace YourProject
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
//other configurations
ConfigureOAuth(app);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
var oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/security/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(2),
Provider = new AuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
try
{
//retrieve your user from database. ex:
var user = await userService.Authenticate(context.UserName, context.Password);
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));
identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
//roles example
var rolesTechnicalNamesUser = new List<string>();
if (user.Roles != null)
{
rolesTechnicalNamesUser = user.Roles.Select(x => x.TechnicalName).ToList();
foreach (var role in user.Roles)
identity.AddClaim(new Claim(ClaimTypes.Role, role.TechnicalName));
}
var principal = new GenericPrincipal(identity, rolesTechnicalNamesUser.ToArray());
Thread.CurrentPrincipal = principal;
context.Validated(identity);
}
catch (Exception ex)
{
context.SetError("invalid_grant", "message");
}
}
}
}
Use the [Authorize] attribute to authorize the actions.
Call api/security/token with GrantType, UserName, and Password to get the bearer token. Like this:
"grant_type=password&username=" + username + "&password=" password;
Send the token within the HttpHeader Authorization as Bearer "YOURTOKENHERE". Like this:
headers: { 'Authorization': 'Bearer ' + token }
Hope it helps!
Since your DB schema are not compatible with default UserStore You must implement your own UserStore and UserPasswordStore classes then inject them to UserManager. Consider this simple example:
First write your custom user class and implement IUser interface:
class User:IUser<int>
{
public int ID {get;set;}
public string Username{get;set;}
public string Password_hash {get;set;}
// some other properties
}
Now author your custom UserStore and IUserPasswordStore class like this:
public class MyUserStore : IUserStore<User>, IUserPasswordStore<User>
{
private readonly MyDbContext _context;
public MyUserStore(MyDbContext context)
{
_context=context;
}
public Task CreateAsync(AppUser user)
{
// implement your desired logic such as
// _context.Users.Add(user);
}
public Task DeleteAsync(AppUser user)
{
// implement your desired logic
}
public Task<AppUser> FindByIdAsync(string userId)
{
// implement your desired logic
}
public Task<AppUser> FindByNameAsync(string userName)
{
// implement your desired logic
}
public Task UpdateAsync(AppUser user)
{
// implement your desired logic
}
public void Dispose()
{
// implement your desired logic
}
// Following 3 methods are needed for IUserPasswordStore
public Task<string> GetPasswordHashAsync(AppUser user)
{
// something like this:
return Task.FromResult(user.Password_hash);
}
public Task<bool> HasPasswordAsync(AppUser user)
{
return Task.FromResult(user.Password_hash != null);
}
public Task SetPasswordHashAsync(AppUser user, string passwordHash)
{
user.Password_hash = passwordHash;
return Task.FromResult(0);
}
}
Now you have very own user store simply inject it to the user manager:
public class ApplicationUserManager: UserManager<User, int>
{
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new MyUserStore(context.Get<MyDbContext>()));
// rest of code
}
}
Also please note you must directly inherit your DB Context class from DbContext not IdentityDbContext since you have implemented own user store.

The entity type IdentityUser is not part of the model for the current context

I see the same issue as this question, but the scenario presented there doesn't seem to apply so I think I have a different issue. In fact, I'm seeing several questions on SO that are similar, each with different causes and solutions, so I think this error must be caused from a high level. That said...
I have an EF code-first database model and I'm trying to use IdentityUser to extend a standard registration for my MVC 5 site.
I have my extended UserModel:
namespace MyMvcSite.Models
{
public class UserModel : IdentityUser
{
public string BillingId { get; set; }
public virtual ICollection<DatabaseModel> Databases { get; set; }
}
}
And my context:
using MyMvcSite.Models;
namespace MyMvcSite.Web
{
public class AuthContext : IdentityDbContext<UserModel>
{
public AuthContext() : base("AuthContext") { }
}
}
Now, when I execute the code to register a user:
public async Task<IdentityResult> RegisterUser(UserModel user)
{
user.Email = user.UserName;
var result = await _userManager.CreateAsync(user);
return result;
}
I get this error:
The entity type IdentityUser is not part of the model for the current context.
I can't figure out what this error means, because it looks like I have everything correct. Can anyone tell what might be going wrong???
I know my connectionString AuthContext is correct because I have used it previously.
When you are using a custom user class with ASP.NET Identity, you have to make sure that you explicitly specify the custom user class type <T> to both the UserManager and the UserStore on instantiation.
private UserManager<UserModel> _userManager;
public AccountController()
{
AuthContext _ctx = new AuthContext();
UserStore<UserModel> userStore = new UserStore<UserModel>(_ctx);
_userManager = new UserManager<UserModel>(userStore);
}
or in shorter form (like your reply comment):
private UserManager<UserModel> _userManager;
public AccountController()
{
AuthContext _ctx = new AuthContext();
_userManager = new UserManager<UserModel>(new UserStore<UserModel>(_ctx));
}
If the type is allowed to defaulted to IdentityUser when you want to use a custom class you will experience the error you reported.
I was having this same problem, and I recall having a similar problem working with SimpleMembership in MVC4.
I’m doing database first development, so I have an EDMX file. Turns out, ASP.NET Identity does not like the connection string that is created when you generate your .edmx model file. If you are using a. EDM connection string in :base(“EDMConnString”) you will most likely have this problem.
I fixed this by creating a standard connection string that pointed to the database where the ASP.NET Identity tables are (in my case the same database), used that connection string in :base, and it worked.
Something like this
<add name="IdentityConnection" connectionString="data source=THEJUS\SQLSERVER2014;initial catalog=IdentitySample;integrated security=True;MultipleActiveResultSets=True;App=IdentitySample.Admin" providerName="System.Data.SqlClient" />
I got this error when I introduced DI to my project. Using AutoFac and Identity I had to add the following: builder.RegisterType<ApplicationDbContext>().As<DbContext>().InstancePerLifetimeScope();
Without this, when AutoFac was creating my UserStore instance, it was using the default UserStore() ctor which creates a new IdentityDbContext, not my ApplicationDbContext.
With this line, UserStore(DbContext context) ctor gets called, with my ApplicationDbContext.
Here is some step i figured out while resolving this issue
First Check your database for Table of ASP.Net Identity
Create these table on your database if not exist you can also apply migration
Check the below image and verify your code
Register Action
IdentityDbContext Class

ASP.NET MVC 5 page hangs on db call

I'm trying to get up and running with an MVC 5 project using ASP.NET Identity 2.0. My starting point is the example app in this tutorial. My initial page is Home/Index. When I try to do a search (expecting a null return value) the app simply hangs and I can't figure out why. Instantiation of the db context is causing the Seed() method to be called, which seems normal, but then it hangs on the roleManager.FindByName(roleName) call (I've commented it in the code below). Pausing the debugger showed where it was stuck but I don't know where to go from there. Relevant classes are as follows.
Controller:
public class HomeController : ApplicationController
{
public ActionResult Index()
{
var user = db.Users.Find("dummyVal");
return View();
}
[Authorize]
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Base Controller:
public abstract class ApplicationController : Controller
{
protected ApplicationDbContext db;
public ApplicationController()
{
db = new ApplicationDbContext();
}
}
DB Initializer:
public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context) {
InitializeIdentityForEF(context);
base.Seed(context);
}
//Create User=Admin#Admin.com with password=Admin#123456 in the Admin role
public static void InitializeIdentityForEF(ApplicationDbContext db) {
var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
const string name = "admin#example.com";
const string password = "Admin#123456";
const string roleName = "Admin";
//Create Role Admin if it does not exist
// EXECUTION HANGS ON THIS CALL...
var role = roleManager.FindByName(roleName);
if (role == null) {
role = new IdentityRole(roleName);
var roleresult = roleManager.Create(role);
}
var user = userManager.FindByName(name);
if (user == null) {
user = new ApplicationUser { UserName = name, Email = name };
var result = userManager.Create(user, password);
result = userManager.SetLockoutEnabled(user.Id, false);
}
// Add user admin to Role Admin if not already added
var rolesForUser = userManager.GetRoles(user.Id);
if (!rolesForUser.Contains(role.Name)) {
var result = userManager.AddToRole(user.Id, role.Name);
}
// Create dummy user
const string dummyUserName = "PeterVenkman";
const string dummyUserPwd = "gf%^^ftf83X";
var dummyUser = userManager.FindByName(dummyUserName);
if (dummyUser == null) {
dummyUser = new ApplicationUser { UserName = dummyUserName, Email = dummyUserName };
var result = userManager.Create(dummyUser, dummyUserPwd);
result = userManager.SetLockoutEnabled(dummyUser.Id, false);
}
}
}
The error is because you are instantiating the dbContext object in the controller and not getting it from the owin context. The presence of two different context objects one in the controller and one in the startup working on the same db is causing this deadlock. Simplest way to resolve this is replace the code
db = new ApplicationDbContext();
with
db = System.Web.HttpContext.Current.GetOwinContext().Get<ApplicationDbContext>();
fixes the issue
I had the same problem and this is how I fixed it.
Delete your current database (I was using .mdf)
Create a new database
Clean and rebuild
Update database - if needed -
Unfortunately, I do not know the source of the problem. Please edit the answer/post a comment if you know :)
For other people with the same issue, make sure the username/password in ApplicationDbInitializer are valid. It will hang if you set the admin password to 123 for example.
Edit: This post provides an explanation + answer
http://forums.asp.net/post/5802779.aspx
Suhas Joshi has the correct answer. The ApplicationDbContext object should be in essence a singleton that is managed by the "Microsoft ASPNET Identity Owin" package (installed using NuGet). When you manually create a new instance of it in your ApplicationController there is contention for the same resource causing this deadlock.
Note that the "Get()" extension used comes from the following library which you must have a reference to in your ApplicationContext class.
using Microsoft.AspNet.Identity.Owin;
Thanks Suhas Joshi for pointing this out as it saved me greatly. I would have just up-voted your answer, but unfortunately I don't have a strong enough reputation yet :).

AddToRole() method doesn't result in db entry in ASP.NET Identity

I'm using ASP.NET Identity in my ASP.NET MVC app. My problem occures while adding user to role. There isn't any exception, but as a result of um.AddToRole() no db entry is added to AspNetUserRoles table. My action method looks like that:
public ActionResult GrantAdmin(string id)
{
ApplicationUser user = um.FindById(id);
if (!rm.RoleExists("admin"))
{
rm.Create(new IdentityRole("admin"));
}
um.AddToRole(user.Id, "admin");
return View((object)user.UserName);
}
um is an object of UserManager class:
private UserManager<ApplicationUser> um = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
What can be a reason of that kind of application behavior? Any idea?
===EDIT===
It is my DbContext:
public ApplicationDbContext()
: base("DefaultConnection")
{
}
And Default Connection in Web.config:
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-RecommendationPlatform-20140404055015.mdf;Initial Catalog=aspnet-RecommendationPlatform-20140404055015;Integrated Security=True" providerName="System.Data.SqlClient" />
When working with ASP.NET Identity it is important to remember that many operations return a result object where eventual errors are stored. There are no exceptions. Therefore one should check the result object for success after every operation. This is true not only for roles but for most methods that save data to the database. Even if it works you should still test for success and eventually throw an exception if the result is not success.
As per the comments in your case the problem was invalid username.

Categories