I am trying to send user to database using identity and sqlite, I am using swagger to contact api. When I click execute my request but I get the following error:
System.TypeInitializationException: The type initializer for 'Microsoft.EntityFrameworkCore.Query.QueryableMethods' threw an exception.
System.InvalidOperationException: Sequence contains more than one matching element
at System.Linq.ThrowHelper.ThrowMoreThanOneMatchException()
at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable1 source, Func2 predicate, Boolean& found)
at System.Linq.Enumerable.Single[TSource](IEnumerable1 source, Func2 predicate)
at Microsoft.EntityFrameworkCore.Query.QueryableMethods..cctor()
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Query.QueryableMethods.get_FirstOrDefaultWithPredicate()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync[TSource](IQueryable1 source, Expression1 predicate, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore6.FindByNameAsync(String normalizedUserName, CancellationToken cancellationToken) at Microsoft.AspNetCore.Identity.UserManager1.FindByNameAsync(String userName)
at Microsoft.AspNetCore.Identity.UserValidator1.ValidateUserName(UserManager1 manager, TUser user, ICollection1 errors) at Microsoft.AspNetCore.Identity.UserValidator1.ValidateAsync(UserManager1 manager, TUser user) at Microsoft.AspNetCore.Identity.UserManager1.ValidateUserAsync(TUser user)
at Microsoft.AspNetCore.Identity.UserManager`1.CreateAsync(TUser user)
at DateApp.Controllers.HomeController.Register() in /Users/michal/Projects/DateApp/DateApp/Controllers/HomeController.cs:line 78
I am expecting to push new user to my sqlite database.
My code - controller:
[HttpPost("add")]
public async Task<IActionResult> Register()
{
if (ModelState.IsValid)
{
var user = new IdentityUser
{
UserName = "admin",
Email = "aadmin#gmail.com"
};
//create without password
var res = await _userManager.CreateAsync(user);
if (!res.Succeeded)
{
var exceptionText = res.Errors.Aggregate("User Creation Failed - Identity Exception. Errors were: \n\r\n\r", (current, error) => current + (" - " + error + "\n\r"));
throw new Exception(exceptionText);
}
}
return Ok();
}
DbContext:
using System;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using DateApp.Models;
namespace DateApp.Db
{
public class AppDBContext : IdentityDbContext
{
private readonly DbContextOptions _options;
public AppDBContext(DbContextOptions options) : base(options)
{
_options = options;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
}
Related
I am trying to map a DataTable to a dto DataTable but facing the error.
I am using CQRS and MediatR.
Here is my code.
I gave a basic implementation here.
Controller.cs
[Route("api/[controller]")]
[ApiController]
public class MapDataTableDemo : ControllerBase
{
TempRepo _tempRepo;
private readonly IMediator _mediator;
public MapDataTableDemo(IMediator mediator)
{
_tempRepo = new TempRepo();
_tempRepo.AddDataToDataTable();
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> MapDataTable()
{
var response = _mediator.Send(new GetDataTableQuery());
return Ok(response);
}
}
GetDataTableQuery.cs
public record GetDataTableQuery:IRequest<ResponseDto>
{
}
GetDataTableHandler.cs
using MediatR;
using MapDataTable.Queries;
using System.Data;
using MapsterMapper;
namespace MapDataTable.Handlers
{
public class GetTableHandler : IRequestHandler<GetDataTableQuery, ResponseDto>
{
TempRepo _tempRepo;
private readonly IMapper _mapper;
public GetTableHandler(IMapper mapper)
{
_tempRepo = new TempRepo();
_tempRepo.AddDataToDataTable();
_mapper = mapper;
}
public async Task<ResponseDto> Handle(GetDataTableQuery request,
CancellationToken cancellationToken)
{
var response = _tempRepo.GetDataTable();
return new ResponseDto()
{
dataTable = _mapper.Map<DataTable>(response)// here i am trying to map datatables
};
}
}
}
ResponseDto.cs
public class ResponseDto
{
public DataTable dataTable { get; set; }
}
TempRepo.cs
using System.Data;
namespace MapDataTable
{
public class TempRepo
{
public DataTable dataTable;
public TempRepo()
{
dataTable = new DataTable();
}
public void AddDataToDataTable()
{
dataTable.Columns.Add("Id");
dataTable.Columns.Add("Name");
dataTable.Columns.Add("Price");
dataTable.Rows.Add(1,"shoes",3000);
dataTable.Rows.Add(2, "mobile", 40000);
dataTable.Rows.Add(3, "watch", 5000);
}
public DataTable GetDataTable()
{
return dataTable;
}
}
}
It gives the following error.
Undocumented
Error: response status is 500
Response body
Download
Newtonsoft.Json.JsonSerializationException: Error getting value from 'Result' on 'System.Threading.Tasks.Task`1[MapDataTable.ResponseDto]'.
---> System.AggregateException: One or more errors occurred. (Error while compiling
source=System.Data.DataTable
destination=System.Data.DataTable
type=Map)
---> Mapster.CompileException: Error while compiling
source=System.Data.DataTable
destination=System.Data.DataTable
type=Map
---> Mapster.CompileException: Error while compiling
source=System.Globalization.CultureInfo
destination=System.Globalization.CultureInfo
type=Map
---> System.InvalidOperationException: No default constructor for type 'CultureInfo', please use 'ConstructUsing' or 'MapWith'
at Mapster.Adapters.BaseAdapter.CreateInstantiationExpression(Expression source, Expression destination, CompileArgument arg)
at Mapster.Adapters.ClassAdapter.CreateInstantiationExpression(Expression source, Expression destination, CompileArgument arg)
at Mapster.Adapters.ClassAdapter.CreateInlineExpression(Expression source, CompileArgument arg)
at Mapster.Adapters.BaseAdapter.CreateInlineExpressionBody(Expression source, CompileArgument arg)
at Mapster.Adapters.BaseAdapter.CreateExpressionBody(Expression source, Expression destination, CompileArgument arg)
at Mapster.Adapters.BaseAdapter.CreateAdaptFunc(CompileArgument arg)
at Mapster.TypeAdapterConfig.CreateMapExpression(CompileArgument arg)
--- End of inner exception stack trace ---
at Mapster.TypeAdapterConfig.CreateMapExpression(CompileArgument arg)
at Mapster.TypeAdapterConfig.CreateInlineMapExpression(Type sourceType, Type destination
How do I map them?
I'm almost there, I implemented my custom authentication using Core Identity because I already have some tables with Users, Roles and a join table with Role assignment to users.
Don't mind bad Model property names, sadly I have to deal with a very badly designed database with not encrypted passwords saved (I did a workaround as you can see in my code below, in order to skip password hashing and it works).
I also started with a very simple hardcoded test for Roles, but I can't reach that code.
I called my DbContext as "OracleContext", so my models are scaffolded from existing db.
I'm using .Net Core 3.1 with Blazor Server, I've created my UserStore object implementing the very minimum amount of interfaces:
UserStore.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace core_identity_utenza_bms.Data
{
public class UserStore : IUserStore<Geutenti>, IUserPasswordStore<Geutenti>, IUserRoleStore<Geutenti>
{
private readonly OracleContext _oracleContext;
public UserStore(OracleContext oracleContext)
{
_oracleContext = oracleContext;
}
public void Dispose()
{
//throw new NotImplementedException();
}
public async Task<string> GetUserIdAsync(Geutenti user, CancellationToken cancellationToken)
{
return user.Cuser.ToString();
}
public async Task<string> GetUserNameAsync(Geutenti user, CancellationToken cancellationToken)
{
return user.Ruser;
}
public async Task SetUserNameAsync(Geutenti user, string userName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<string> GetNormalizedUserNameAsync(Geutenti user, CancellationToken cancellationToken)
{
return user.Tuser;
}
public async Task SetNormalizedUserNameAsync(Geutenti user, string normalizedName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<IdentityResult> CreateAsync(Geutenti user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<IdentityResult> UpdateAsync(Geutenti user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<IdentityResult> DeleteAsync(Geutenti user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<Geutenti> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
var userQuery =
await _oracleContext.Geutenti.Where(
u =>
u.Cuser.ToString().Equals(userId) &&
u.Bstor.Equals("A") &&
u.Csoci.Equals("MASM")).FirstOrDefaultAsync(cancellationToken);
return userQuery;
}
public async Task<Geutenti> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
var userQuery =
await _oracleContext.Geutenti.Where(
u =>
u.Ruser.Contains("ARDILLOI") &&
u.Bstor.Equals("A") &&
u.Csoci.Equals("MASM"))
.FirstOrDefaultAsync(cancellationToken);
return userQuery;
}
public async Task SetPasswordHashAsync(Geutenti user, string passwordHash, CancellationToken cancellationToken)
{
//throw new NotImplementedException();
}
public async Task<string> GetPasswordHashAsync(Geutenti user, CancellationToken cancellationToken)
{
return user.CpaswDuser;
}
public async Task<bool> HasPasswordAsync(Geutenti user, CancellationToken cancellationToken)
{
return !(user.CpaswDuser == null || user.CpaswDuser.Equals(string.Empty));
}
/*
* UserRole
*/
public async Task AddToRoleAsync(Geutenti user, string roleName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task RemoveFromRoleAsync(Geutenti user, string roleName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<IList<string>> GetRolesAsync(Geutenti user, CancellationToken cancellationToken)
{
//var userRoles = _oracleContext.Geruxute.
return new List<string> {"BURBERO", "BARBARO"};
}
public async Task<bool> IsInRoleAsync(Geutenti user, string roleName, CancellationToken cancellationToken)
{
return new List<string> { "BURBERO", "BARBARO" }.Contains(roleName);
}
public async Task<IList<Geutenti>> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}
Then I implemented a class for skipping password hashing:
NoPasswordHasher.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace core_identity_utenza_bms.Data
{
public class NoPasswordHasher : IPasswordHasher<Geutenti>
{
public string HashPassword(Geutenti user, string password)
{
return password;
}
public PasswordVerificationResult VerifyHashedPassword(Geutenti user, string hashedPassword, string providedPassword)
{
return hashedPassword.Equals(providedPassword) ? PasswordVerificationResult.Success : PasswordVerificationResult.Failed;
}
}
}
I created a RoleStore.cs file for retrieving my Roles on db:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace core_identity_utenza_bms.Data
{
public class RoleStore : IRoleStore<Geruoliz>
{
private readonly OracleContext _oracleContext;
public RoleStore(OracleContext oracleContext)
{
_oracleContext = oracleContext;
}
public void Dispose()
{
//throw new NotImplementedException();
}
public async Task<IdentityResult> CreateAsync(Geruoliz role, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<IdentityResult> UpdateAsync(Geruoliz role, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<IdentityResult> DeleteAsync(Geruoliz role, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<string> GetRoleIdAsync(Geruoliz role, CancellationToken cancellationToken)
{
return role.Crule.ToString();
}
public async Task<string> GetRoleNameAsync(Geruoliz role, CancellationToken cancellationToken)
{
return role.Rrule;
}
public async Task SetRoleNameAsync(Geruoliz role, string roleName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<string> GetNormalizedRoleNameAsync(Geruoliz role, CancellationToken cancellationToken)
{
return role.Rrule;
}
public async Task SetNormalizedRoleNameAsync(Geruoliz role, string normalizedName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<Geruoliz> FindByIdAsync(string roleId, CancellationToken cancellationToken)
{
var role = await _oracleContext.Geruoliz.Where(
r =>
r.Crule.ToString().Equals(roleId) &&
r.Bstor.Equals("A") &&
r.Csoci.Equals("MASM"))
.FirstOrDefaultAsync(cancellationToken: cancellationToken);
return role;
}
public async Task<Geruoliz> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken)
{
var role = await _oracleContext.Geruoliz.Where(
r =>
r.Rrule.Equals(normalizedRoleName) &&
r.Bstor.Equals("A") &&
r.Csoci.Equals("MASM"))
.FirstOrDefaultAsync(cancellationToken: cancellationToken);
return role;
}
}
}
And finally i edited Startup.cs:
services.AddDbContext<OracleContext>();
services
.AddDefaultIdentity<Geutenti>()
.AddUserStore<UserStore>();
services.AddScoped<IPasswordHasher<Geutenti>, NoPasswordHasher>();
By now I can't even enter the breakpoints in UserStore.GetRolesAsync and UserStore.IsInRoleAsync, as I imagine I should pass by these checks in order to determine if a view is visible or not, for example by employing:
#page "/counter"
<h1>Counter</h1>
<p>Current count: #currentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
<AuthorizeView>
<p>sei loggato.</p>
</AuthorizeView>
<AuthorizeView Roles="BURBERO">
<Authorized>
Sei un burbero!
</Authorized>
<NotAuthorized>
Sei una brava persona.
</NotAuthorized>
</AuthorizeView>
#code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
I only succeed in Authorize view not specifying any role.
The last thing I tried is to edit services like:
services.AddDbContext<OracleContext>();
services
.AddDefaultIdentity<Geutenti>()
.AddUserStore<UserStore>()
.AddRoleStore<RoleStore>();
services.AddScoped<IPasswordHasher<Geutenti>, NoPasswordHasher>();
But it gives me exception:
System.InvalidOperationException: 'No RoleType was specified, try AddRoles<TRole>().'
By now I honestly feel overwhelmed by scattered information around the web.
Any help? Thank you very much for your time!
I found my way around. It was both a problem cache related but also as Startup setting.
All the code above is working, I had to set in Startup:
// Add identity types
services.AddDbContext<OracleContext>();
services
.AddIdentity<Geutenti, Geruoliz>()
.AddRoles<Geruoliz>()
.AddDefaultTokenProviders();
// Identity Services
services.AddTransient<IUserStore<Geutenti>, UserStore>();
services.AddTransient<IRoleStore<Geruoliz>, RoleStore>();
services.AddScoped<IPasswordHasher<Geutenti>, NoPasswordHasher>();
The objects here are:
OracleContext: it's the database context object from Entity Framework Core that wraps up my db that contains my three user tables
Geutenti: Users table, with all related data (username, password, email, etc.)
Geruoliz: Roles table
UserStore and RoleStore: objects that implement those interfaces that inform Core Identity how to access user-related data for authentication and authorization
NoPasswordHasher: workaround to skip password hashing. (DESCLAIM: this introduces a security hole, you should never save passwords as-is, but their hash)
I had to logout/erase cookies and restart the project, login et voilĂ !
I am using ASP.NET Web API, after throwing exception, I am able to catch it in GlobalExceptionHandler, but I get CORS error and can't enter App_error. I tried multiple solutions, nothing is working, right now I have this flow.
Custom Exception is thrown in controller, then we enter GlobalExceptionHandler:
public class GlobalExceptionHandler : IExceptionHandler
{
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
ExceptionDispatchInfo.Capture(context.Exception).Throw();
return Task.CompletedTask;
}
}
And it is not going further, I get CORS on front.
Any solution?
You could do something like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;
namespace Web.API.Handler
{
public class ErrorHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new TextPlainErrorResult()
{
Request = context.ExceptionContext.Request,
Content = "Oops! Sorry! Something went wrong." + "Please contact support so we can try to fix it."
};
}
private class TextPlainErrorResult : IHttpActionResult
{
public HttpRequestMessage Request { get; set; }
public string Content { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.InternalServerError, Content);
//CORS Support
if (!response.Headers.Contains("Access-Control-Allow-Origin"))
response.Headers.Add("Access-Control-Allow-Origin", "*");
return Task.FromResult(response);
}
}
}
}
I managed to resolve problem by doing everything inside lifter, so App_error is not used anymore
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly Logger m_Logger = LogManager.GetCurrentClassLogger();
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
var exception = context.Exception;
var statusCode = HttpStatusCode.InternalServerError;
if (exception != null)
{
var errorMessage = exception.Message;
if (exception is BaseException be)
{
statusCode = be.StatusCode;
}
m_Logger.Error(exception, context.Request.RequestUri.AbsoluteUri);
var response = context.Request.CreateResponse(statusCode, new { errorMessage });
context.Result = new ResponseMessageResult(response);
}
return Task.CompletedTask;
}
}
I had an app in .NET Framework in which I implemented OAuthAuthorizationServer. Now I want to upgrade my app to .NET Core 2.1, so I did some R&D and decided to use ASOS. Now the issue is I have implemented ASOS and it is working fine but I have some chunks that I can't figure out how to convert.
private Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType),
context.Scope.Select(x => new Claim("claim", x)));
context.Validated(identity);
return Task.FromResult(0);
}
private Task GrantClientCredetails(OAuthGrantClientCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType),
context.Scope.Select(x => new Claim("claim", x)));
context.Validated(identity);
return Task.FromResult(0);
}
private readonly ConcurrentDictionary<string, string> _authenticationCodes =
new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
private void CreateAuthenticationCode(AuthenticationTokenCreateContext context)
{
context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
_authenticationCodes[context.Token] = context.SerializeTicket();
}
private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context)
{
string value;
if (_authenticationCodes.TryRemove(context.Token, out value))
{
context.DeserializeTicket(value);
}
}
private void CreateRefreshToken(AuthenticationTokenCreateContext context)
{
context.SetToken(context.SerializeTicket());
}
private void ReceiveRefreshToken(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
Now I have couple of question:
Client Credentials and Resource owner password grant types are two different grant types so how can we differentiate in them using ASOS?
GrantResourceOwnerCredentials takes OAuthGrantResourceOwnerCredentialsContext as a param and GrantClientCredentials takes OAuthGrantClientCredentialsContext as a param. Both these contexts contains scope which is not available in ASOS.
How can I serialize and deserialize access and refresh tokens like I was doing OAuthAuthorizationProvider?
How do we handle refresh tokens in ASOS? I can see refresh tokens in response but I haven't write any logic for refresh token my self.
Client Credentials and Resource owner password grant types are two different grant types so how can we differentiate in them using ASOS?
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsClientCredentialsGrantType())
{
// ...
}
else if (context.Request.IsPasswordGrantType())
{
// ...
}
else
{
throw new NotSupportedException();
}
}
GrantResourceOwnerCredentials takes OAuthGrantResourceOwnerCredentialsContext as a param and GrantClientCredetails takes OAuthGrantClientCredentialsContext as a param. Both these contexts contains scope which is not available in ASOS
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
var scopes = context.Request.GetScopes();
// ...
}
How can I serialize and deserialize access and refresh tokens like I was doing OAuthAUthorizationProvider?
By using the OnSerializeAccessToken/OnDeserializeAccessToken and OnSerializeRefreshToken/OnDeserializeRefreshToken events.
How do we handle refresh tokens in ASOS? I can see refresh tokens in response but I haven't write any logic for refresh token my self.
Unlike Katana's OAuth server middleware, ASOS provides default logic for generating authorization codes and refresh tokens. If you want to use implement things like token revocation, you can do that in the events I mentioned. Read AspNet.Security.OpenIdConnect.Server. Refresh tokens for more information.
Here's an example that returns GUID refresh tokens and stores the associated (encrypted) payload in a database:
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace AuthorizationServer
{
public class MyToken
{
public string Id { get; set; }
public string Payload { get; set; }
}
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options) { }
public DbSet<MyToken> Tokens { get; set; }
}
public class MyProvider : OpenIdConnectServerProvider
{
private readonly MyDbContext _database;
public MyProvider(MyDbContext database)
{
_database = database;
}
public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
{
if (!context.Request.IsPasswordGrantType() && !context.Request.IsRefreshTokenGrantType())
{
context.Reject(error: OpenIdConnectConstants.Errors.UnsupportedGrantType);
}
else
{
// Don't enforce client authentication.
context.Skip();
}
return Task.CompletedTask;
}
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
if (context.Request.Username == "bob" && context.Request.Password == "bob")
{
var identity = new ClaimsIdentity(context.Scheme.Name);
identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Subject, "Bob"));
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), identity.AuthenticationType);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OfflineAccess);
context.Validate(ticket);
}
else
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The username/password couple is invalid.");
}
}
else
{
var token = await _database.Tokens.FindAsync(context.Request.RefreshToken);
_database.Tokens.Remove(token);
await _database.SaveChangesAsync();
context.Validate(context.Ticket);
}
}
public override async Task SerializeRefreshToken(SerializeRefreshTokenContext context)
{
context.RefreshToken = Guid.NewGuid().ToString();
_database.Tokens.Add(new MyToken
{
Id = context.RefreshToken,
Payload = context.Options.RefreshTokenFormat.Protect(context.Ticket)
});
await _database.SaveChangesAsync();
}
public override async Task DeserializeRefreshToken(DeserializeRefreshTokenContext context)
{
context.HandleDeserialization();
var token = await _database.Tokens.FindAsync(context.RefreshToken);
if (token == null)
{
return;
}
context.Ticket = context.Options.RefreshTokenFormat.Unprotect(token.Payload);
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
{
options.UseInMemoryDatabase(nameof(MyDbContext));
});
services.AddAuthentication()
.AddOpenIdConnectServer(options =>
{
options.TokenEndpointPath = "/token";
options.ProviderType = typeof(MyProvider);
options.AllowInsecureHttp = true;
})
.AddOAuthValidation();
services.AddMvc();
services.AddScoped<MyProvider>();
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
}
}
}
I'm trying to create a .net Core solution using Identity w/o EntityFramework but with integer ids (UserId and RoleId). I've followed this basic setup for creating the solution and removing EF (https://taherchhabra.github.io/jekyll/update/2016/09/22/aspnet-core-identity-without-entityframework.html) but this still has the stupid Guid id column for the Users. When I try to change it I run into an issue in creating my UserStore because it inherits from IUserStore<User> which requires that I implement
Task<User> FindByIdAsync(string userId, CancellationToken cancellationToken)
which or course means the Id value is a string instead of an int.
I have read several things talking about how to change EF-based solutions to use ints and I dug into how EF does that but it is all based on EF classes which use a TKey.
I tried to create my own IUserStore to change the strings to ints but the project wouldn't even spin up (it would compile fine but when I tried to run it locally it would exit before completing spin up).
Does anyone know how to accomplish this? I've search quite a bit and can't find anything.
Edit:
What I have done so far is create my own UserStore and RollStore (which are basically just copies of the originals which I will tweak once I get working). In my HomeController I added the following:
public class HomeController : Controller
{
private readonly UserManager<models.User> _userManager;
public HomeController(UserManager<models.User> userManager)
{
_userManager = userManager;
}
public async Task<IActionResult> Index()
{
var user = new models.User();
if(User.Identity.IsAuthenticated)
user = await _userManager.FindByNameAsync(User.Identity.Name);
return View();
}
}
This is basically a "is it plugged in" test which I can step through in the debugger.
In my Startup.cs:
services.AddSingleton<IUserStore<User>, UserStore>();
services.AddSingleton<IRoleStore<Role>, RoleStore>();
services.AddIdentity<User, Role>()
.AddDefaultTokenProviders();
This will actually work (debugging through my endpoint, the user is populated correctly). But, if I look close I realize that IUserStore and IRoleStore are the Microsoft.AspNetCore.Identity versions which means I'm still dependent on those contracts and am required to use the Guid for an Id.
If I force the issue, using
services.AddSingleton<SandboxCoreDapper.Identity.Interfaces.IUserStore<User>, UserStore>();
services.AddSingleton<SandboxCoreDapper.Identity.Interfaces.IRoleStore<Role>, RoleStore>();
then when I try to hit my endpoint, the UserManager will fail to resolve:
"Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore1[SandboxCoreDapper.Models.User]' while attempting to activate 'Microsoft.AspNetCore.Identity.UserManager1[SandboxCoreDapper.Models.User]'."
Edit 2
I found this interesting discussion: https://github.com/aspnet/Identity/issues/493
Based on that I've gone ahead and created my own RoleManager and UserManager which basically just extend the Identity versions:
namespace SandboxCoreDapper.Identity.Services
{
public class UserManager<TUser> : Microsoft.AspNetCore.Identity.UserManager<TUser> where TUser : class
{
public UserManager(
sandyId.IUserStore<TUser> store, // my version of IUserStore
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<TUser> passwordHasher,
IEnumerable<IUserValidator<TUser>> userValidators,
IEnumerable<IPasswordValidator<TUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<TUser>> logger)
:base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
}
}
}
and likewise for RoleManager.
Then I added to Startup.cs
services.AddIdentity<User, Role>()
.AddDefaultTokenProviders()
.AddUserManager<SandboxCoreDapper.Identity.Services.UserManager<User>>()
.AddRoleManager<SandboxCoreDapper.Identity.Services.RoleManager<Role>>()
;
So far so good...
This is in response to my comment, and your question/comment:
Just declare your own UserStore, then register it in the Startup.cs.
Here's my implementation of the UserStore to accommodate this scenario:
public class UserStore : IUserPasswordStore<Login>, IUserEmailStore<Login>, IUserPhoneNumberStore<Login>
{
private readonly IAccountRepo _accountRepo;
public UserStore(IAccountRepo accountRepo)
{
_accountRepo = accountRepo;
}
public void Dispose()
{
}
public async Task<string> GetUserIdAsync(Login user, CancellationToken cancellationToken)
{
return await Task.FromResult(user.Username);
}
public async Task<string> GetUserNameAsync(Login user, CancellationToken cancellationToken)
{
return await Task.FromResult(user.Username);
}
public Task SetUserNameAsync(Login user, string userName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetNormalizedUserNameAsync(Login user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task SetNormalizedUserNameAsync(Login user, string normalizedName, CancellationToken cancellationToken)
{
await Task.FromResult(user.Username = normalizedName.ToLower());
}
public async Task<IdentityResult> CreateAsync(Login user, CancellationToken cancellationToken)
{
await _accountRepo.CreateLogin(user);
return IdentityResult.Success;
}
public async Task<IdentityResult> UpdateAsync(Login user, CancellationToken cancellationToken)
{
await _accountRepo.UpdateLogin(user.LoginId, user.Email, true);
return IdentityResult.Success;
}
public Task<IdentityResult> DeleteAsync(Login user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public async Task<Login> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
return await _accountRepo.GetUser(userId);
}
public async Task<Login> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
return await _accountRepo.GetUser(normalizedUserName);
}
public async Task SetPasswordHashAsync(Login user, string passwordHash, CancellationToken cancellationToken)
{
if (user.LoginId != 0)
{
await _accountRepo.ChangePassword(user.Email, user.Username, passwordHash);
}
user.PasswordHash = passwordHash;
}
public async Task<string> GetPasswordHashAsync(Login user, CancellationToken cancellationToken)
{
return await Task.FromResult(user.PasswordHash);
}
public async Task<bool> HasPasswordAsync(Login user, CancellationToken cancellationToken)
{
return await Task.FromResult(!string.IsNullOrEmpty(user.PasswordHash) && !string.IsNullOrEmpty(user.Salt));
}
public Task SetEmailAsync(Login user, string email, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetEmailAsync(Login user, CancellationToken cancellationToken)
{
return Task.FromResult(user.Email);
}
public async Task<bool> GetEmailConfirmedAsync(Login user, CancellationToken cancellationToken)
{
return await Task.FromResult(user.EmailConfirmed);
}
public Task SetEmailConfirmedAsync(Login user, bool confirmed, CancellationToken cancellationToken)
{
return Task.FromResult(user.EmailConfirmed = confirmed);
}
public Task<Login> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetNormalizedEmailAsync(Login user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task SetNormalizedEmailAsync(Login user, string normalizedEmail, CancellationToken cancellationToken)
{
return Task.FromResult(user.Email = normalizedEmail.ToLower());
}
public Task SetPhoneNumberAsync(Login user, string phoneNumber, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetPhoneNumberAsync(Login user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<bool> GetPhoneNumberConfirmedAsync(Login user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task SetPhoneNumberConfirmedAsync(Login user, bool confirmed, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
And the Startup.cs ConfigureServices method:
services.AddSingleton<IUserStore<Login>, UserStore>();
The closest I could come to an answer for this is this git project: https://github.com/grandchamp/Identity.Dapper
It is still in process and I am working with the project owner on some enhancements but for anyone else who stumbles on this question, right now that project is your best bet.