Net Core 3.1 API with Identity Server 4 custom password validation - asp.net-core-webapi

I'm building an API using identity server and I need to use an existing database. The users password are stored with a custom hash password.
I use FindClientByIdAsync to validate user and password, but as the password is encrypted in a non-standard algorithm I get invalid_client error message. If I change in execution time (with breakpoint) the value of password for an unencrypted value the authentication works.
It's possible change the client_secret validation for FindClientByIdAsync?
Custom ClientStore class
public class ClientStore : IClientStore
{
private readonly IMyUserRepository myUserRepository;
public ClientStore(IMyUserRepository myUserRepository)
{
this.myUserRepository = myUserRepository;
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId()
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("My_API", "My API")
};
}
public async Task<Client> FindClientByIdAsync(string client)
{
var user = await myUserRepository.GetUserByEmailAsync(client);
if (user == null)
return null;
return new Client()
{
ClientId = client,
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
ClientSecrets =
{
new Secret(user.Password.Sha256()) //if I change to unencrypted works, but the value in database is hashed
},
AllowedScopes = { "GOLACO_API", IdentityServerConstants.StandardScopes.OpenId }
};
}
}
Identity server configuration in Startup class
services.AddIdentityServer(options =>
{
options.Events.RaiseSuccessEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseErrorEvents = true;
})
.AddSigningCredential(GetSigningCredential()) // here I just read the private.key file
.AddInMemoryIdentityResources(ClientStore.GetIdentityResources())
.AddInMemoryApiResources(ClientStore.GetApiResources())
.AddClientStore<ClientStore>();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["Configuration"];
options.ApiName = "My_API";
options.RequireHttpsMetadata = false;
});
services.AddAuthentication()
.AddFacebook("Facebook", options =>
{
options.AppId = "1234";
options.AppSecret = "1234567890";
});
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();

You have to implement IResourceOwnerPasswordValidator as Damien showed in his blog
public class CustomResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private readonly IUserRepository _userRepository;
public CustomResourceOwnerPasswordValidator(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
if (_userRepository.ValidateCredentials(context.UserName, context.Password))
{
var user = _userRepository.FindByUsername(context.UserName);
context.Result = new GrantValidationResult(user.SubjectId, OidcConstants.AuthenticationMethods.Password);
}
return Task.FromResult(0);
}
}
And add builder.AddResourceOwnerValidator<CustomResourceOwnerPasswordValidator>(); in the startup file.

Related

AuthorizeView Roles doesn't recognize role even though the code does

I'm trying to set up authorization with Blazor .net core 3.1 and the AuthorizeView Roles doesn't seem to recognize the role that is in the database. On the other hand, if I try to do it in code and I've set up a little message if it finds the role with the user, it finds the role and displays the ""User is a Valid User" message. I'm using this with AzureAd Microsoft authentication and AspNetCore identity package.
Here's the index page code
#page "/"
#using Microsoft.AspNetCore.Authorization;
#using Microsoft.AspNetCore.Identity;
#inject UserManager<IdentityUser> _UserManager
#inject RoleManager<IdentityRole> _RoleManager
#inject AuthenticationStateProvider AuthenticationStateProvider
#attribute [Authorize]
<span>#Message</span>
<AuthorizeView Roles="Users">
<Authorized>
<p>Youre In!</p>
</Authorized>
</AuthorizeView>
#code
{
[CascadingParameter]
private Task<AuthenticationState> authStateTask { get; set; }
string USER_ROLE = "Users";
string CurrentEmail;
string Message;
protected override async Task OnInitializedAsync()
{
var authState = await authStateTask;
var CurrentEmail = authState.User.Identity.Name;
if (CurrentEmail.Contains("#users.com") == true)
{
var user = await _UserManager.FindByNameAsync(CurrentEmail);
if (user == null)
{
var newUser = new IdentityUser { UserName = CurrentEmail, Email = CurrentEmail };
var createResult = await _UserManager.CreateAsync(newUser);
if (createResult.Succeeded)
{
var roleResult = await _UserManager.AddToRoleAsync(newUser, USER_ROLE);
if (roleResult.Succeeded)
{
Message = ("Good job");
}
}
}
else
{
var RoleResult = await _UserManager.IsInRoleAsync(user, USER_ROLE);
if(RoleResult == true)
{
Message = "User is a Valid User";
}
else
{
Message = "User is invalid";
}
}
}
}
}
Here are my services:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options => options.UseSqlite("DataSource=db.db"));
services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>();
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
}

How to fix 'Unauthorized Error 401', even when logged in?

I'm creating this webpage in Angular with .NET as my backend. Everything was working just fine, but today (all of a sudden) I'm getting Unauthorized errors (401). When I remove my authorization, I'm getting internal server errors (meaning my 'User.Identity.Name') does not work. So I'm guessing there is a problem with my JWT
I have tried to remove authorization headers. But that gave me an internal server error (500). The only requests that work are those not using IdentityUser and thus being [AllowAnonymous]
I have no idea what the problem could be, so it's hard to show some code. But since I think the problem has something to do with my JWT I'll add some code of that.
Some controller code .NET
[Route("api/[controller]")]
[ApiController]
[Produces("application/json")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class BierenController : ControllerBase
{
private readonly IBierRepository _bierRepository;
private readonly IGebruikerRepository _gebruikerRepository;
private readonly IBrouwerRepository _brouwerRepository;
public BierenController(IBierRepository bierRepository, IGebruikerRepository gebruikerRepository, IBrouwerRepository brouwerRepository)
{
_bierRepository = bierRepository;
_gebruikerRepository = gebruikerRepository;
_brouwerRepository = brouwerRepository;
}
[HttpGet]
[AllowAnonymous]
public IEnumerable<Bier> GetBieren()
{
return _bierRepository.GetAll().OrderBy(b => b.Naam);
}
[HttpGet("{id}")]
[AllowAnonymous]
public ActionResult<Bier> GetBier(int id)
{
Bier bier = _bierRepository.GetBy(id);
if (bier == null)
return NotFound();
return bier;
}
[HttpPost]
[Authorize(Roles = "Brouwer, Admin")]
public IActionResult PostBier(Bier bier)
{
Brouwer brouwer = _brouwerRepository.GetBy(User.Identity.Name);
brouwer.AddBier(bier);
_brouwerRepository.SaveChanges();
return NoContent();
}
Startup.cs configuration
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<BrouwerContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("BierContext")));
services.AddScoped<IBrouwerRepository, BrouwerRepository>();
services.AddScoped<IBierRepository, BierRepository>();
services.AddScoped<IGebruikerRepository, GebruikerRepository>();
services.AddScoped<IPostRepository, PostRepository>();
services.AddOpenApiDocument(c =>
{
c.Title = "BabAPI";
c.Version = "v1";
c.Description = "De API voor BAB";
c.DocumentProcessors.Add(new SecurityDefinitionAppender("JWT Token", new SwaggerSecurityScheme
{
Type = SwaggerSecuritySchemeType.ApiKey,
Name = "Authorization",
In = SwaggerSecurityApiKeyLocation.Header,
Description = "Copy 'Bearer' + valid JWT token into field" }));
c.OperationProcessors.Add(new OperationSecurityScopeProcessor("JWT Token"));
});
services.AddCors(options =>
options.AddPolicy("AllowAllOrigins", builder =>
builder.AllowAnyOrigin()
));
services.AddDefaultIdentity<IdentityUser>(cfg => cfg.User.RequireUniqueEmail = true).AddRoles<IdentityRole>().AddEntityFrameworkStores<BrouwerContext>();
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
options.User.RequireUniqueEmail = true;
});
services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false; x.SaveToken = true; x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"])),
ValidateIssuer = false,
ValidateAudience = false,
RequireExpirationTime = true
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider services)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseMvc();
CreateUserRoles(services).Wait();
app.UseAuthentication();
app.UseSwaggerUi3();
app.UseSwagger();
app.UseCors("AllowAllOrigins");
}
Angular authentication interceptor
export class AuthenticationInterceptor implements HttpInterceptor {
constructor(private authService: AuthenticationService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService.token.length) {
const clonedRequest = req.clone({
headers: req.headers.set('Authorization', `Bearer ${this.authService.token}`)
});
return next.handle(clonedRequest);
}
return next.handle(req);
}
}
.NET Login/Register/Tokencreation
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult<String>> CreateToken(LoginDTO model)
{
var user = await this._userManager.FindByNameAsync(model.GebruikersNaam);
if(user == null)
{
user = await this._userManager.FindByEmailAsync(model.GebruikersNaam);
}
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (result.Succeeded)
{
string token = await GetTokenAsync(user);
return Created("", token);
}
}
return BadRequest();
}
private async Task<string> GetTokenAsync(IdentityUser user)
{
var role = await _userManager.GetRolesAsync(user);
IdentityOptions _options = new IdentityOptions();
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName),
new Claim(_options.ClaimsIdentity.RoleClaimType, role.FirstOrDefault())
}),
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256),
Expires = DateTime.Now.AddMinutes(30)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return token;
}
[AllowAnonymous]
[HttpPost("register")]
public async Task<ActionResult<String>> Register(RegisterDTO model)
{
IdentityUser user = new IdentityUser { UserName = model.GebruikersNaam, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
if (model.Email.ToLower().Equals("admin1#hotmail.com"))
{
Brouwer brouwer = new Brouwer { Naam = model.GebruikersNaam, Email = model.Email };
_brouwerRepository.Add(brouwer);
_brouwerRepository.SaveChanges();
await _userManager.AddToRoleAsync(user, "Admin");
}
else if (model.Email.ToLower().Equals("brouwer1#hotmail.com"))
{
Brouwer brouwer = new Brouwer { Naam = model.GebruikersNaam, Email = model.Email };
_brouwerRepository.Add(brouwer);
_brouwerRepository.SaveChanges();
await _userManager.AddToRoleAsync(user, "Brouwer");
}
else
{
Gebruiker gebruiker = new Gebruiker { Email = model.Email, Gebruikersnaam = model.GebruikersNaam };
_gebruikerRepository.Add(gebruiker);
_gebruikerRepository.SaveChanges();
await _userManager.AddToRoleAsync(user, "User");
}
string token = await GetTokenAsync(user);
return Created("", token);
}
else {
return BadRequest();
}

Authentication and LoginPath for different areas in ASP.NET Core 2

ASP.NET Core 2
Help me to configure AddAuthentication for two routes: users (user accounts) and admin area.
For example, if user doesn't signed in and trying to enter /Account/Orders/ he'll be redirected to /Account/SignIn/.
But if someone trying access /Admin/Orders/ must be redireted to /Admin/Signin/
Have not found ay solution ATM.
Solved!
In admin area (controllers) we using Authorize attr. arg.: [Authorize(AuthenticationSchemes = "backend")] and that is.
BTW we are able to make any tuning by accessing HttpContext in AddCookie's options and events.
Configuration:
services
.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o =>
{
o.LoginPath = new PathString("/account/login/");
})
.AddCookie("backend", o =>
{
o.LoginPath = new PathString("/admin/account/login/");
});
#Alex's answer got me 90% of the way there.
In .Net 6, I'm using this https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0 approach to use Cookies without using the Username setup in identity.
Program.cs
var authentication = services.AddAuthentication(o =>
{
o.DefaultScheme = AuthenticationSchemes.FrontEnd;
});
authentication.AddCookie(AuthenticationSchemes.FrontEnd, o =>
{
o.LoginPath = CookieAuthenticationDefaults.LoginPath;
});
authentication.AddCookie(AuthenticationSchemes.BackEnd, o =>
{
o.LoginPath = new PathString("/admin/login/");
o.AccessDeniedPath = new PathString("/admin/accessdenied");
});
AppAuthenticationSchemes.cs
public class AuthenticationSchemes
{
public const string FrontEnd = "Frontend";
public const string BackEnd = "Backend";
public const string Either = FrontEnd + "," + BackEnd;
}
AccountController.cs
[AllowAnonymous]
public class AccountController : Controller
{
private readonly FrontEndSecurityManager _frontEndSecurityManager;
public AccountController(FrontEndSecurityManager frontEndSecurityManager)
{
_frontEndSecurityManager = frontEndSecurityManager;
}
[HttpPost(Name = "Login")]
public async Task<ActionResult> Login(LoginViewModel loginModel)
{
if (string.IsNullOrEmpty(loginModel.Username) ||
string.IsNullOrEmpty(loginModel.Password))
{
ModelState.AddModelError("form", "Please enter Username and Password");
return RedirectToAction("Login", "Account");
}
var loginResult = await _frontEndSecurityManager.ValidateCredentials(loginModel.Username, loginModel.Password);
if (!loginResult.IsSuccess)
{
this.AddFlash(FlashMessageType.Danger, "UserName or Password is incorrect");
return RedirectToAction("Login", "Account");
}
var identity = await _frontEndSecurityManager.CreateIdentityAsync(loginModel.Username, loginResult);
await _frontEndSecurityManager.SignInAsync(identity, HttpContext);
return RedirectToAction("Menu", "App");
}
}
FrontEndSecurityManager.cs
public class FrontEndSecurityManager
{
private readonly SignInApi _api;
private readonly AuthenticationOptions _authenticationOptions;
public FrontEndSecurityManager(SignInApi api, IOptions<AuthenticationOptions> authenticationOptions)
{
_api = api;
_authenticationOptions = authenticationOptions.Value;
}
public async Task SignInAsync(ClaimsIdentity identity, HttpContext httpContext)
{
var authProperties = new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(30),
IsPersistent = true,
IssuedUtc = DateTimeOffset.UtcNow
};
await httpContext.SignOutAsync(AuthenticationSchemes.BackEnd);
await httpContext.SignInAsync(AuthenticationSchemes.FrontEnd, new ClaimsPrincipal(identity), authProperties);
}
public async Task<LoginResult> ValidateCredentials(string username, string password)
{
if (_authenticationOptions.DemoUserEnabled)
{
if (string.Equals(username, "demo", StringComparison.InvariantCultureIgnoreCase))
{
var result = new LoginResult(StandardResults.SuccessResult, "")
{
Employee_Name = "Demo User",
Employee_Email = "DemoGuy#gmail.com",
Employee_Initials = "DG",
Employee_Type = "Regular",
Role1 = true,
Role2 = true,
Role3 = false
};
return result;
}
}
var apiRequest = new LoginRequest() { Username = username, Password = password };
var loginResult = await _api.LoginAsync(apiRequest);
if (loginResult.Success)
{
return loginResult.Data;
}
else
{
return LoginResult.Failure();
}
}
public async Task<ClaimsIdentity> CreateIdentityAsync(string username, LoginResult loginResult)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Role, AppRoles.User),
new Claim(ClaimTypes.Email, loginResult.Employee_Email, ClaimValueTypes.Email),
new Claim(ClaimTypes.GivenName, loginResult.GivenName),
new Claim(ClaimTypes.Surname, loginResult.Surname),
new Claim(AppClaimTypes.EmployeeType, loginResult.Employee_Type),
new Claim(AppClaimTypes.EmployeeInitials, loginResult.Employee_Initials),
new Claim(AppClaimTypes.Location, loginResult.Location.ToString(), ClaimValueTypes.Integer),
};
if (loginResult.Use_Checkin)
{
claims.Add(new Claim(ClaimTypes.Role, AppRoles.Checkin));
}
if (loginResult.Use_Pickup)
{
claims.Add(new Claim(ClaimTypes.Role, AppRoles.Pickup));
}
var identity = new ClaimsIdentity(claims, AuthenticationSchemes.FrontEnd);
return identity;
}
public void SignOutAsync(HttpContext httpContext)
{
httpContext.SignOutAsync(AuthenticationSchemes.FrontEnd);
}
}
From here, you could easily extrapolate how you want the back-end authentication controller to work. Essentially something like
await HttpContext.SignOutAsync(AuthenticationSchemes.FrontEnd);await HttpContext.SignInAsync(AuthenticationSchemes.BackEnd, new ClaimsPrincipal(identity), authProperties);
An example of using each policy would be
[Authorize(AuthenticationSchemes = AuthenticationSchemes.BackEnd)]
public IActionResult Secure()
{
return View("Secure");
}
or
[Authorize] // scheme not explicit, so Pr DefaultScheme = AuthenticationSchemes.FrontEnd is used
[Route("[controller]")]
public class OutgoingController : Controller
{
}

I want web (MVC) and API on same database

I have a database, need web interface for administration and management (number of transactions, billing, and other administration) and to serve data from database (products) "manually" and API to serve data (products) to other bigger clients. All secured by SSL and https obviously.
I made a asp.net MVC 5 app (business logic and administration) and wanted to implement API (noob in API) for delivering data to users.
Have no idea how to implement security from MVC to API(same database).
The app is small and I can rewrite it. I'm thinking to try with core, but fear that i will be stuck with same problem.
Concrete question: What approach do I take and weather it should be in MVC 5 generation or .core (MVC 6) to be able to use one database for data, users and their authorizations?
(pushing everything true API is something I would like to avoid)
Ok, my project is done. I moved ahead on MVC 5.
(I apologize to you perfectionists, but I don't have the time now to strip unnecessary thing so I dumped whole files as they are :)
1st approach - abandoned
First I tried designing it as is recommended true the internet: .MVC solution, .DB for database and .API solution.
Conclusion - A lot of problems wit authentication and entity framework. At the end I abandoned this approach
2nd and successful approach
Just one solution .MVC
True NuGet installed .net Api, used integrated authorization extended using few tutorials (not single one worked). Note that I use ASP.NET Identity 2.0 Extending Identity Models and Using Integer Keys Instead of Strings and Implementing HTTPS Everywhere in ASP.Net MVC application.
Here are modifications and addons:
App_Start -> IdentityConfig.cs
public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
// *** ADD INT TYPE ARGUMENT TO CONSTRUCTOR CALL:
public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
: base(store)
{
}
public static ApplicationUserManager Create(
IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
// *** PASS CUSTOM APPLICATION USER STORE AS CONSTRUCTOR ARGUMENT:
var manager = new ApplicationUserManager(
new ApplicationUserStore(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
// *** ADD INT TYPE ARGUMENT TO METHOD CALL:
manager.UserValidator = new UserValidator<ApplicationUser, int>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
// other code removed for brevity
manager.UserLockoutEnabledByDefault = Convert.ToBoolean(ConfigurationManager.AppSettings["UserLockoutEnabledByDefault"].ToString());
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(Double.Parse(ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"].ToString()));
manager.MaxFailedAccessAttemptsBeforeLockout = Convert.ToInt32(ConfigurationManager.AppSettings["MaxFailedAccessAttemptsBeforeLockout"].ToString());
// Register two factor authentication providers.
// This application uses Phone and Emails as a step of receiving a
// code for verifying the user You can write your own provider and plug in here.
// *** ADD INT TYPE ARGUMENT TO METHOD CALL:
//manager.RegisterTwoFactorProvider("PhoneCode",
// new PhoneNumberTokenProvider<ApplicationUser, int>
// {
// MessageFormat = "Your security code is: {0}"
// });
//// *** ADD INT TYPE ARGUMENT TO METHOD CALL:
//manager.RegisterTwoFactorProvider("EmailCode",
// new EmailTokenProvider<ApplicationUser, int>
// {
// Subject = "SecurityCode",
// BodyFormat = "Your security code is {0}"
// });
//manager.EmailService = new EmailService();
//manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
// *** ADD INT TYPE ARGUMENT TO METHOD CALL:
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser, int>(
dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
// PASS CUSTOM APPLICATION ROLE AND INT AS TYPE ARGUMENTS TO BASE:
public class ApplicationRoleManager : RoleManager<ApplicationRole, int>
{
// PASS CUSTOM APPLICATION ROLE AND INT AS TYPE ARGUMENTS TO CONSTRUCTOR:
public ApplicationRoleManager(IRoleStore<ApplicationRole, int> roleStore)
: base(roleStore)
{
}
// PASS CUSTOM APPLICATION ROLE AS TYPE ARGUMENT:
public static ApplicationRoleManager Create(
IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
{
return new ApplicationRoleManager(
new ApplicationRoleStore(context.Get<ApplicationDbContext>()));
}
}
public class EmailService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
// Plug in your email service here to send an email.
return Task.FromResult(0);
}
}
public class SmsService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
// Plug in your sms service here to send a text message.
return Task.FromResult(0);
}
}
//This is useful if you do not want to tear down the database each time you run the application.
//public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
//This example shows you how to create a new database if the Model changes
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
//InitializeIdentityForEF(context); //- Do not Seed - IGOR
//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 = "igor#email.mail";
// const string password = "LolLol1";
// const string roleName = "lol";
// //Create Role Admin if it does not exist
// var role = roleManager.FindByName(roleName);
// if (role == null)
// {
// role = new ApplicationRole(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);
// }
//}
}
public class ApplicationSignInManager : SignInManager<ApplicationUser, int>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) :
base(userManager, authenticationManager)
{ }
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
}
}
App_Start -> Startup.Auth.cs
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
validateInterval: TimeSpan.FromMinutes(2880),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
// Need to add THIS line because we added the third type argument (int) above:
getUserIdCallback: (claim) => int.Parse(claim.GetUserId()))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseGoogleAuthentication(
// clientId: "",
// clientSecret: "");
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"), //TODO - makni ovo
AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
}
//public partial class Startup
//{
// public void ConfigureAuth(IAppBuilder app)
// {
// // Enable the application to use a cookie to store information for the signed in user
// app.UseCookieAuthentication(new CookieAuthenticationOptions
// {
// ExpireTimeSpan = TimeSpan.FromHours(24),
// CookieSecure = CookieSecureOption.Never,
// CookieHttpOnly = false,
// SlidingExpiration = true,
// AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
// LoginPath = new PathString("/Account/Login")
// });
// // Use a cookie to temporarily store information about a user logging in with a third party login provider
// app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// }
//}
App_Start -> WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// TODO: Add any additional configuration code.
// Web API routes
config.MapHttpAttributeRoutes();
//config.Routes.MapHttpRoute(
// name: "getkey",
// routeTemplate: "api/ApiKeys/Get/{term}"
//);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// WebAPI when dealing with JSON & JavaScript!
// Setup json serialization to serialize classes to camel (std. Json format)
var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
formatter.SerializerSettings.ContractResolver =
new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
// make all web-api requests to be sent over https
config.MessageHandlers.Add(new EnforceHttpsHandler());
}
}
MySysAdmin controller that I use for initial insert and edit of roles and initial user.
public SysAdminController(ApplicationUserManager userManager,
ApplicationRoleManager roleManager)
{
UserManager = userManager;
RoleManager = roleManager;
}
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
set
{
_userManager = value;
}
}
private ApplicationRoleManager _roleManager;
public ApplicationRoleManager RoleManager
{
get
{
return _roleManager ?? HttpContext.GetOwinContext().Get<ApplicationRoleManager>();
}
private set
{
_roleManager = value;
}
}
public ActionResult RoleIndex()
{
return View(RoleManager.Roles);
}
public ActionResult RoleCreate()
{
return View();
}
[HttpPost]
public async Task<ActionResult> RoleCreate(SysAdminVM.RoleViewModel roleViewModel)
{
if (ModelState.IsValid)
{
// Use ApplicationRole, not IdentityRole:
var role = new ApplicationRole(roleViewModel.Name);
var roleresult = await RoleManager.CreateAsync(role);
if (!roleresult.Succeeded)
{
ModelState.AddModelError("", roleresult.Errors.First());
return View();
}
return RedirectToAction("RoleIndex");
}
return View();
}
public async Task<ActionResult> RoleEdit(int id)
{
if (id > 0)
{
var role = await RoleManager.FindByIdAsync(id);
if (role == null)
{
return HttpNotFound();
}
SysAdminVM.RoleViewModel roleModel = new SysAdminVM.RoleViewModel { Id = role.Id, Name = role.Name };
return View(roleModel);
}
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> RoleEdit([Bind(Include = "Name,Id")] SysAdminVM.RoleViewModel roleModel)
{
if (ModelState.IsValid)
{
var role = await RoleManager.FindByIdAsync(roleModel.Id);
role.Name = roleModel.Name;
await RoleManager.UpdateAsync(role);
return RedirectToAction("RoleIndex");
}
return View();
}
[AllowAnonymous]
public async Task<ActionResult> Initialize()
{
if (db.App.Where(x => x.Name.Contains("Initialize")).FirstOrDefault() == null)
{
await InitRoleCreate();
await InitUser();
db.App.Add(
new App { Name = "Initialize", Val = "true" }
);
db.SaveChanges();
return View();
}
return HttpNotFound();
}
private async Task InitRoleCreate()
{
var model = new List<string>()
{
"SysAdmin",
"Admin",
"User",
};
foreach (var item in model)
{
var role = new ApplicationRole(item);
await RoleManager.CreateAsync(role);
}
}
private async Task InitUser()
{
var user = new ApplicationUser
{
UserName = "HerGiz",
Email = "hergiz#outlook.com",
Name = "Igor Hermanović",
Contact = "098 185 3131",
TwoFactorEnabled = false,
LockoutEnabled = true,
EmailConfirmed = true
};
var adminResult = await UserManager.CreateAsync(user, "W7xtc2ywfb");
await UserManager.AddToRolesAsync(user.Id, "SysAdmin");
}
}
Entire API part that I need - controller and out of the box login (that is berried somewhere):
[Authorize]
public class ApiKeysController : ApiController
{
[Authorize]
[Route("api/getkey/{term}")]
public ShowFullKeyVM Get(string term)
{
if (User.Identity.IsAuthenticated == true)
{
if (!string.IsNullOrWhiteSpace(term) && (term.Length == 15 || term.Length == 16))
{
var lKey = new LKey();
var vm = lKey.Search(term);
if (vm != null)
{
return vm;
}
}
return new ShowFullKeyVM() { Error = "IMEI either is not valid :(", SearchIMEI = term };
}
return new ShowFullKeyVM() { Error = "Not Authenticated!!!", SearchIMEI = term };
}
}
Global.asax
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
MvcHandler.DisableMvcResponseHeader = true;
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
App_Start.AutoMapperConfig.DefineMaps();
ModelBinders.Binders.Add(typeof(decimal), new Extensions.DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new Extensions.DecimalModelBinder());
}
Web.config
<appSettings>
<add key="UserLockoutEnabledByDefault" value="true" />
<add key="DefaultAccountLockoutTimeSpan" value="30" />
<add key="MaxFailedAccessAttemptsBeforeLockout" value="4" />
</appSettings>

Token auth in asp mvc 6

It seems like there is not a lot of information about how to do authorization with the new MVC version. Since ASP 5 now is in RC 1 one could guess that you now can start trying to understand how its going to work...
What I want to do is just a simple example of an auth token that contains the user's name and roles.
A link like http://bitoftech.net/2015/03/11/asp-net-identity-2-1-roles-based-authorization-authentication-asp-net-web-api/ would help greatly but seems hard to find
You can try OpenIddict for that. You need RC2 to use it, but it's quite easy to set up:
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddOpenIddict(); // Add the OpenIddict services after registering the Identity services.
}
public void Configure(IApplicationBuilder app) {
app.UseOpenIddict();
}
Sean Walsh posted a detailed walkthrough on his blog: http://capesean.co.za/blog/asp-net-5-jwt-tokens/.
You can use OpenIdConnect.Server. You can set it up like this
Startup.cs
public class Startup {
public IConfigurationRoot configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) {
IConfigurationBuilder builder = new ConfigurationBuilder();
configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services) {
services.AddIdentity<ApplicationUser, IdentityRole>(options => {
options.User.RequireUniqueEmail = true;
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireNonLetterOrDigit = false;
options.Password.RequiredLength = 6;
}).AddEntityFrameworkStores<DataModelContext>();
}
public void Configure(IApplicationBuilder app) {
app.UseJwtBearerAuthentication(new JwtBearerOptions {
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Audience = "OAuth:Audience",
Authority = "OAuth:Authority",
RequireHttpsMetadata = false
});
app.UseOpenIdConnectServer(options => {
options.Issuer = new Uri("OpenId:Issuer");
options.AllowInsecureHttp = true;
options.AuthorizationEndpointPath = PathString.Empty;
options.Provider = new AuthorizationProvider();
});
}
}
AuthorizationProvider.cs
public class AuthorizationProvider : OpenIdConnectServerProvider {
public override Task ValidateTokenRequest(ValidateTokenRequestContext context) {
context.Skip();
return Task.FromResult(0);
}
public override Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context) {
string username = context.UserName;
string password = context.Password;
UserManager<ApplicationUser> userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
ApplicationUser user = userManager.FindByNameAsync(username).Result;
if (userManager.CheckPasswordAsync(user, password).Result) {
ClaimsIdentity identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.Name, username,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
List<string> roles = userManager.GetRolesAsync(user).Result.ToList();
foreach (string role in roles) {
identity.AddClaim(ClaimTypes.Role, role,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
AuthenticationTicket ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
ticket.SetResources("OAuth:Audience");
List<string> scopes = new List<string>();
if (context.Request.HasScope("offline_access")) {
scopes.Add("offline_access");
}
ticket.SetScopes(scopes);
context.Validate(ticket);
} else {
context.Reject("invalid credentials");
}
return Task.FromResult(0);
}
}
Then on the Controller or Action you want to use Authorization, you can use the Authorize Attribute like this
[Authorize(Roles = "Administrator")]
public void MyAction() { }

Resources