Adding extra authorization layer in Blazor - components

I have an AuthorizeView component based on authentication through an OpenID Connect provider, but I want to add an extra layer based on some informations found about the user in the associated database after OIDC authentication. I am wondering whether the logic illustrated below,
1: Would be the smartest way of implementing this?
2: And if so, how to approach the task of building my own authorization view inside an already existing one?
<AuthorizeView>
<Authorized>
<ExtraLayerAuthorized>
<p>Authorized through OIDC and extra layer</p>
</ExtraLayerAuthorized>
<NotExtraLayerAuthorized>
<p>Authorized through OIDC, but NOT extra layer</p>
</NotExtraLayerAuthorized>
</Authorized>
<NotAuthorized>
<p>No authorization</p>
</NotAuthorized>
</AuthorizeView>`

Note sure if the is Server or WASM.
One good way to do this is just to add an additional ClaimsIdentity to the existing ClaimsPrincipal of the standard AuthenticationStateProvider.
Here's how to do it in Server. See the comments for an explanation of what it does.
public class MyAuthenticationStxateProvider : ServerAuthenticationStateProvider
{
public async override Task<AuthenticationState> GetAuthenticationStateAsync()
{
// Call the base to get the AuthState and the user provided in the Security Headers by the server
var authstate = await base.GetAuthenticationStateAsync();
var user = authstate.User;
if (user?.Identity?.IsAuthenticated ?? false)
{
// Do whatever you want here to retrieve the additional user information you want to
// include in the ClaimsPrincipal - probably some form of Identity Service
// Construct a ClaimsIdentity instance to attach to the ClaimsPrincipal
// I just added a role as an example
var myIdentity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "User") });
// Add it to the existing ClaimsPrincipal
user.AddIdentity(myIdentity);
}
// construct a new state with the updated ClaimsPrincipal
// - or an empty one of you didn't get a user in the first place
// All the Authorization components and classes will now use this ClaimsPrincipal
return new AuthenticationState(user ?? new ClaimsPrincipal());
}
}
This is setup to use Auth0 as the authentication provider.
"AllowedHosts": "*",
"Auth0": {
"Domain": "xxxxx.eu.auth0.com",
"ClientId": "xxxxxxxxxxxxxxxxxxxxxx",
}
The service registration looks something like this:
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services
.AddAuth0WebAppAuthentication(options => {
options.Domain = builder.Configuration["Auth0:Domain"];
options.ClientId = builder.Configuration["Auth0:ClientId"];
});
builder.Services.AddAuthorization();
builder.Services.AddScoped<AuthenticationStateProvider, MyAuthenticationStateProvider>();
Login.cshtml.cs
public class LoginModel : PageModel
{
public async Task OnGet(string redirectUri)
{
await HttpContext.ChallengeAsync("Auth0", new
AuthenticationProperties
{ RedirectUri = redirectUri });
}
}
Logout.cshtml.cs
public class LogoutModel : PageModel
{
public async Task<IActionResult> OnGetAsync()
{
await HttpContext.SignOutAsync();
return Redirect("/");
}
}
And then this page demonstrates logging in and out and adding the second identity when we have a valid user.
#page "/"
#inject NavigationManager NavManager
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<div>
<button class="btn btn-danger" #onclick=this.LogOut>Log Out</button>
<button class="btn btn-primary" #onclick=this.LogIn>Log In</button>
</div>
<h2>Claims</h2>
<dl>
#foreach (var claim in user.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
</dl>
#code {
private ClaimsPrincipal user = new ClaimsPrincipal();
[CascadingParameter] private Task<AuthenticationState> authStateProvider { get; set; } = default!;
protected override async Task OnInitializedAsync()
{
var authState = await authStateProvider;
user = authState.User;
}
private void LogIn()
{
var returnUrl = NavManager.Uri;
NavManager.NavigateTo($"login?redirectUri={returnUrl}", forceLoad: true);
}
private void LogOut()
{
var returnUrl = NavManager.Uri;
NavManager.NavigateTo($"logout?redirectUri={returnUrl}", forceLoad: true);
}
}

Related

Umbraco + OpenId + Thinktecture Puzzle

I have been trying to figure this out for 2 days now and have decided it time to ask for help. Here is the setup:
Running Umbraco 7.5.6 with the following packages:
UmbracoIdentity 5.0.0
UmbracoCms.IdentityExtensions 1.0.0
UmbracoCms.IdentityExtesnions.AzureActiveDirectory 1.0.0
We are also running a Thinktecture SSO Server
IdentityServer3
Here are the requirements:
Back Office Users must log in via AAD or Internal Users (this is done
and working)
Members must log in via the Thinktecture SSO Server
If the member is not on the home page, they must be redirected back to whatever page they were attempting to access after successful login
This all seems straight forward so here is the code I have so far.
This is the Middleware I wrote to stick into the Owin Startup Process:
public static IAppBuilder ConfigureFrontEndSsoAuth(this IAppBuilder app)
{
//var postLoginRedirectUrl = "";
var ssoOptions = new OpenIdConnectAuthenticationOptions
{
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
Authority = Config.SsoAuthority,
ClientId = Config.SsoClientId,
CallbackPath = new PathString("/umbraco/surface/UmbracoIdentityAccount/ExternalLoginCallback"),
RedirectUri = "http://bacp.dev/umbraco/surface/UmbracoIdentityAccount/ExternalLoginCallback",
ResponseType = Config.SsoResponseType,
Scope = Config.SsoScope,
AuthenticationMode = AuthenticationMode.Passive,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = async x =>
{
// Will deal with Claims soon
}
}
};
ssoOptions.Caption = "Member SSO";
ssoOptions.AuthenticationType = String.Format(CultureInfo.InvariantCulture, Config.SsoAuthority);
ssoOptions.SetExternalSignInAutoLinkOptions(new ExternalSignInAutoLinkOptions(autoLinkExternalAccount: true));
app.UseOpenIdConnectAuthentication(ssoOptions);
return app;
}
Here are my two controller methods:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl = null)
{
if (returnUrl.IsNullOrWhiteSpace())
{
returnUrl = Request.RawUrl;
}
// Request a redirect to the external login provider
return new ChallengeResult(provider,
Url.SurfaceAction<UmbracoIdentityAccountController>("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
}
[HttpGet]
[HttpPost]
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl = null)
{
if (String.IsNullOrEmpty(returnUrl))
{
returnUrl = "/";
}
var loginInfo = await OwinContext.Authentication.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
//go home, invalid callback
return RedirectToLocal(returnUrl);
}
// Sign in the user with this external login provider if the user already has a login
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null)
{
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
else
{
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
And last, the login action on the view:
<form action="/Umbraco/surface/UmbracoIdentityAccount/ExternalLogin" method="post">
#Html.AntiForgeryToken()
<input type="hidden" name="provider" value="#Config.SsoAuthority"/>
<input type="hidden" name="returnUrl" value="#Request.RawUrl"/>
<input type="submit" class="profile-summary__link" value="Login"/>
</form>
Now, this is where I get lost and I am either just missing something really small or something.
The following steps are the issue at hand:
The Umbraco Page Loads up
I am able to click on "Login" which redirects to the SSO Server
If I am not logged in, I login | If I am logged in, it validated my cookie and sends me back
It claims it's sending me to ExternalLoginCallback but if I put a breakpoint on the controller method it never hits hit.
It then tries to redirect back to ExternalLogin (not sure where it's getting this from)
Any help or suggestions would be great.

Bot Framework - Sign-In Card, how get auth result

working with Microsoft Bot Framework V3 I started using Sign-In Cards.
I did a simple cut and paste from example code page into my code and let's say it works (compiles):
https://docs.botframework.com/en-us/csharp/builder/sdkreference/attachments.html
What was expected is a behavior similar to oauth process so to be redirected to , do it's own stuffs and return the auth resul including all informations.
What I realized is that it simply open a new web page to the link I provided, that's all...
No other code founded elsewere...
So far it seems useless as I could provide the link simply with normal messages based on this behavior, also there is no communication with the bot.
Did I missed something?
Option 1) Custom Authentication using Windows Active Directory
I have made a custom authentication technique which queries Windows AD using Kerberos LDAP Protocol and using PrincipalContext class.
Firstly, in Root Dialog save the context of the chat in the ConversationReference and encode it using Base64 encoding.
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.ConnectorEx;
using System.Threading;
namespace ADAuthBot.Dialogs
{
[Serializable]
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to Auth Bot!");
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var message = await result as Activity;
ConversationReference conversationReference = message.ToConversationReference();
string username = string.Empty;
context.PrivateConversationData.SetValue<string>("usertext", message.Text);
if (!context.PrivateConversationData.TryGetValue<string>("Username", out username))
{
string encodedCookie = UrlToken.Encode(conversationReference);
await AuthDialog.createPromptForLogin(context, encodedCookie);
}
else
{
context.Call(this, ResumeAfter);
}
}
private async Task ResumeAfter(IDialogContext context, IAwaitable<object> result)
{
var item = await result;
context.Wait(MessageReceivedAsync);
}
}
}
Next, we come to the Auth Dialog in which we create a Sign-In Card and give the URL page that needs to be opened on the click of the authenticate button.
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.ConnectorEx;
using System.Threading;
using System.Collections.Generic;
using System.Configuration;
namespace ADAuthBot.Dialogs
{
[Serializable]
public class AuthDialog: IDialog<object>
{
static string authenticationUrl = string.Empty; //Authentication URL is the MVC View URL, which will have the username and password window.
static string callbackurl = string.Empty;
static AuthDialog()
{
authenticationUrl = ConfigurationManager.AppSettings["AuthenticationUrl"];
callbackurl = ConfigurationManager.AppSettings["AuthCallbackUrl"];
}
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
}
public static async Task createPromptForLogin(IDialogContext context, string encodedCookie)
{
IMessageActivity response = context.MakeMessage();
response.Attachments = new List<Attachment>();
SigninCard signincard = new SigninCard()
{
Text = "Click here to sign in",
Buttons = new List<CardAction>() {
new CardAction()
{
Title = "Authentication Required",
Type = ActionTypes.OpenUrl,
Value = $"{authenticationUrl}?{encodedCookie}"
}
}
};
response.Attachments.Add(signincard.ToAttachment());
await context.PostAsync(response);
}
}
}
Next I made a MVC view which inputs your username and password and sends it to the ADAuthController to query it against the Windows Active Directory.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace ADAuthService.Controllers
{
public class LoginADController : Controller
{
// GET: LoginAD
[Route("Login")]
public ActionResult LoginUsingAD()
{
return View();
}
}
}
Next I created a simple Razor view which uses jQuery AJAX call to send username and password by encoding it in base64 encoding by using Javascript's btoa() function.
<script src="~/scripts/jquery-3.2.1.min.js"></script>
<script src="~/scripts/bootstrap.min.js"></script>
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />
<script>
$(function () {
$("#txtUserName").html("");
$("#txtPassword").html("");
function make_base64_auth(username, password) {
var tok = username + ' ' + password;
var hash = btoa(tok);
return hash;
}
$("#btnSubmit").click(function () {
var userName = $("#txtUserName").val();
var passWord = $("#txtPassword").val();
var conversationReference = $(location).attr('search');
console.log(conversationReference);
var dataToBeSent = {
"ConversationReference": conversationReference,
"HashedUserCredentials": make_base64_auth(userName, passWord)
};
$.ajax({
url: "http://localhost:1070/api/Login",
method: "POST",
dataType: "json",
data: dataToBeSent,
contentType: "application/json",
crossDomain: true,
success: function (data) {
debugger;
console.log(data);
if(!$.isEmptyObject(data))
alert(data);
},
error: function (jqXHR, textStatus, errorThrown) {
debugger;
if (!$.isEmptyObject(jqXHR))
alert("Something happened wrong because: " + jqXHR.responseText);
}
});
});
});
</script>
<div class="panel-info">
<div class="panel panel-heading">
Enter your credentials
</div>
<div class="panel panel-body">
<div class="form-group">
<label for="username">Username: </label> <input id="txtUserName" type="text" placeholder="Enter username" required class="form-control" />
<label for="password">Password: </label> <input id="txtPassword" type="password" placeholder="Enter password" required class="form-control" />
<button id="btnSubmit" class="btn btn-info">Submit</button>
<button id="btnReset" class="btn btn-danger" type="reset">Reset</button>
</div>
</div>
</div>
I made a model class to store whether a user is identified or not.
namespace ADAuthService.Models
{
public class AuthenticatedUser
{
public string AuthenticatedUserName { get; set; } = string.Empty;
public bool IsAuthenticated { get; set; } = false;
}
}
and a model class to get details from MVC View.
namespace ADAuthService.Models
{
public class UserDetailsHashed
{
public string HashedUserCredentials { get; set; } = string.Empty;
public string ConversationReference { get; set; } = string.Empty;
}
}
Now the main content is to write a method which queries the Windows Active Directory by taking username, password and domain as input. After authenticating I am using the Service URL to send the authenticated user's name to the bot framework by resolving the scope using Autofac IoC Container.
using ADAuthService.Models;
using Autofac;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Cors;
namespace ADAuthService.Controllers
{
public class ADAuthController : ApiController
{
[NonAction]
private void extractUserDetailsFromHash(UserDetailsHashed userDetails, out string username, out string password, out string conversationReference)
{
try
{
string[] userCredentials = userDetails.HashedUserCredentials.Split(' ');
byte[] userCredentialsBinary = Convert.FromBase64String(userCredentials.Last());
string decodedString = Encoding.UTF8.GetString(userCredentialsBinary);
string[] decodedStringArray = decodedString.Split(' ');
username = decodedStringArray[0];
password = decodedStringArray[1];
string[] userConversationReference = userDetails.ConversationReference.Split('?');
conversationReference = userConversationReference[1];
}
catch (Exception ex)
{
throw ex;
}
}
[NonAction]
private Task<AuthenticatedUser> ValidateUserAgainstAD(string username, string password)
{
AuthenticatedUser user = new AuthenticatedUser();
return Task.Run<AuthenticatedUser>(() => {
string ADDisplayName = string.Empty;
try
{
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, System.Environment.UserDomainName))
{
bool isValidCredentials = ctx.ValidateCredentials(username, password, ContextOptions.Negotiate);
// Additional check to search user in directory.
if (isValidCredentials)
{
UserPrincipal prUsr = new UserPrincipal(ctx);
prUsr.SamAccountName = username;
PrincipalSearcher srchUser = new PrincipalSearcher(prUsr);
UserPrincipal foundUsr = srchUser.FindOne() as UserPrincipal;
if (foundUsr != null)
{
user.AuthenticatedUserName = foundUsr.DisplayName;
user.IsAuthenticated = isValidCredentials;
}
}
else
throw new AuthenticationException($"Couldn't query no such credentials in Microsoft Active Directory such as Username: {username} and Password: {password}. Try entering a valid username and password combination.");
}
}
catch (Exception ex)
{
throw ex;
}
return user;
});
}
[NonAction]
public async Task ReplyToBot(string userName, string encodedConversationReference)
{
Activity reply = null;
ConversationReference decodedConversationReference = UrlToken.Decode<ConversationReference>(encodedConversationReference);
bool writeSuccessful = false;
IMessageActivity msgToBeSent = decodedConversationReference.GetPostToUserMessage();
using (ILifetimeScope scope = DialogModule.BeginLifetimeScope(Conversation.Container, msgToBeSent))
{
try
{
IConnectorClient client = scope.Resolve<IConnectorClient>();
IStateClient sc = scope.Resolve<IStateClient>();
BotData userData = sc.BotState.GetPrivateConversationData(msgToBeSent.ChannelId, msgToBeSent.From.Id, msgToBeSent.Id);
userData.SetProperty("Username", userName);
sc.BotState.SetPrivateConversationData(msgToBeSent.ChannelId, msgToBeSent.Conversation.Id, msgToBeSent.Id, userData);
writeSuccessful = true;
}
catch (Exception ex)
{
writeSuccessful = false;
throw ex;
}
if (!writeSuccessful)
{
msgToBeSent.Text = string.Empty;
await Conversation.ResumeAsync(decodedConversationReference, msgToBeSent);
}
if (writeSuccessful)
{
reply = msgToBeSent as Activity;
var connector = new ConnectorClient(new Uri(msgToBeSent.ServiceUrl));
reply.Text = $"Welcome {userName}!";
connector.Conversations.SendToConversation(reply);
}
}
}
[HttpPost]
[EnableCors("*", "*", "*")]
[Route("api/Login")]
public async Task<HttpResponseMessage> Login(UserDetailsHashed userDetails)
{
try
{
string username = string.Empty;
string password = string.Empty;
string conversationReference = string.Empty;
AuthenticatedUser userToBeAuthenticated = new AuthenticatedUser();
extractUserDetailsFromHash(userDetails, out username, out password, out conversationReference);
userToBeAuthenticated = await ValidateUserAgainstAD(username, password);
if (userToBeAuthenticated.IsAuthenticated)
{
await ReplyToBot(userName: userToBeAuthenticated.AuthenticatedUserName, encodedConversationReference: conversationReference);
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent($"Thanks, {userToBeAuthenticated.AuthenticatedUserName} you're now logged in!") };
}
else
{
return new HttpResponseMessage { StatusCode = HttpStatusCode.Forbidden, Content = new StringContent($"Couldn't query no such credentials in Microsoft Active Directory such as Username: {username} and Password: {password}. Try entering a valid username and password combination.") };
}
}
catch(Exception ex)
{
throw new HttpResponseException(new HttpResponseMessage() { StatusCode = HttpStatusCode.Forbidden, Content = new StringContent($"Couldn't query no such credentials in Microsoft Active Directory. Try entering a valid username and password combination.") });
}
}
}
}
Option 2) Use the patterns described in the following link:
MSDN Magic number Pattern
No, you didn't miss anything. The Sign-In card just provide a visual way of showing to the user that it needs to authenticate. Each channel will display the Sign-In card differently; depending on the channel implementation.
To implement the OAuth process, I'd recommend you to take a look to AuthBot.
AuthBot is a .Net library for Azure Active Directory authentication on
bots built via Microsoft Bot Framework.
Even if you are not using AAD, the library is still useful to get the idea on how the OAuth process can be implemented. Aditionally, AuthBot is also using Sign-In card in some scenarios to ask the user for authentication (see this code).
There are other samples that can also help you to understand how to build the OAuth process:
Simple Facebook Auth Sample
GraphBot

UserId not found error in aspnet Identity at GenerateUserIdentityAsync method

I am getting UserId not found error after registring a user and also after login.Moreoever, after registration, data is saved to database and in dbo.AspNetUsers table, id column is auto incremented and return type is int.
There is UserId Column in AspNetUserClaims table.It has 4 Col---Id,UserId,ClaimType,ClaimValue.It has Id column as auto incremented not the userId.
I was initially successfully changed Primary key from string to int by following this link---http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity.
It was running succesfully before but now it is giving me error at this line---
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
Exception Details: System.InvalidOperationException: UserId not found.
This is the complete stack trace. you can see it here----http://pastebin.com/0hp5eAnp
It was working fine earlier but now when i added foreign key relationship with other tables, i don't know what is missing there. In the database all the tables are created properly with proper relationship between them but something is missing here.
My ApplicationUser class is something like this-------
public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public ApplicationUser()
{
this.Posts = new HashSet<Post>();
}
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public class CustomUserRole : IdentityUserRole<int> { }
public class CustomUserClaim : IdentityUserClaim<int> { }
public class CustomUserLogin : IdentityUserLogin<int> { }
public class CustomRole : IdentityRole<int, CustomUserRole>
{
public CustomRole() { }
public CustomRole(string name) { Name = name; }
}
public class CustomUserStore : UserStore<ApplicationUser, CustomRole, int,
CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public CustomUserStore(ApplicationDbContext context)
: base(context)
{
}
}
public class CustomRoleStore : RoleStore<CustomRole, int, CustomUserRole>
{
public CustomRoleStore(ApplicationDbContext context)
: base(context)
{
}
}
and my IdentityConfig.cs class file is something like this-------
// Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new CustomUserStore(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser, int>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 1,
//RequireNonLetterOrDigit = true,
//RequireDigit = true,
//RequireLowercase = true,
//RequireUppercase = true,
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
// 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 it in here.
manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser, int>
{
MessageFormat = "Your security code is {0}"
});
manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser, int>
{
Subject = "Security Code",
BodyFormat = "Your security code is {0}"
});
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser, int>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
// Configure the application sign-in manager which is used in this application.
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);
}
}
i have seen many stackoverflow answers but not getting it to work.Can someone plzz plzz see what is missing, what should i do now.thanks in advance.
Here, in the applicationUser class, at the Id column, it showing some warning and message in tooltip like this-------
models.ApplicationUSer.ID hides inherited member
Microsoft.Aspnet.Identity.EntityFramework.IDentity
USer.Id. To make current member override
that implementation, add override keyword otherwise
add new keyword where x is just the namespace.
My StartUp.Auth.cs in App_Start folder is like this------
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.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(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager), getUserIdCallback:(id)=>(id.GetUserId<int>()))
}
});
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: "");......................................................................
and my startUp.cs file is like this----
[assembly: OwinStartupAttribute(typeof(WebApp.Startup))]
namespace WebApp
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
You will have to pull out your ApplicationUserManager to have it nice and clean and implement more methods... For example see following post (It implemented all methods with your custom Key (TKey in the example):
http://www.symbolsource.org/MyGet/Metadata/aspnetwebstacknightly/Project/Microsoft.AspNet.Identity.Core/2.0.0-beta1-140211/Release/Default/Microsoft.AspNet.Identity.Core/Microsoft.AspNet.Identity.Core/UserManager.cs?ImageName=Microsoft.AspNet.Identity.Core
You will see that the error you receive GetSecurityStampAsync also is implemented there.

MVC 5 ASP.NET Identity 2: Capture user's preference for "remember me" in ExternalLogin

I am using the Identity 2.0 Sample.
I get that by setting isPersistent to true in ExternalLoginCallback action method, the browser will automatically log the user in the next time (within limits) they visit using the same browser. I know that if the user's "remember me" preference was captured and passed to the ExternalLogin action method that it could be put into returnUrl and accessed in ExternalLoginCallback. But how do I get their preference to the ExternalLogin action method?
I don't get in this case how to put a checkbox on the LoginView page and wire things up so that I can process it in the ExternalLogin action method. How can I accomplish this?
Check out the AccountController.ExternalLoginConfirmation action and note the call to await SignInHelper.SignInAsync(user, isPersistent: false, rememberBrowser: false). You can set those values to true, or you can update the ExternalLoginConfirmationViewModel and corresponding ExternalLoginConfirmation view to let the user decide.
BTW: isPersistent will persist the users session across closing and reopening their browser. The rememberBrowser argument is particular to two factor authentication and it sounds like should be left false for your circumstance.
Tangentially related Supporting remember me with two factor authentication
Don't delete any code, just change as follows:
In AccountViewModels, edit to match:
public class ExternalLoginViewModel
{
public string Action { get; set; }
public string ReturnUrl { get; set; }
public string RemembermeExtnl { get; set; }
}
In Account Controller, edit to match:
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
ViewBag.RemembermeExtnl = "f";
return View();
}
public ActionResult ExternalLogin(string provider, string returnUrl, string remembermeExtnl)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl, remembermeExtnl = remembermeExtnl }));
}
public async Task<ActionResult> ExternalLoginCallback(string returnUrl, string remembermeExtnl)
{
...
var result = await SignInHelper.ExternalSignIn(loginInfo, isPersistent: remembermeExtnl=="t");
...
}
In Login view, edit to match:
<section id="socialLoginForm">
#Html.Partial("_ExternalLoginsListPartial", new PG.Models.ExternalLoginViewModel() { Action = "ExternalLogin", ReturnUrl = ViewBag.ReturnUrl, RemembermeExtnl = ViewBag.RemembermeExtnl })
<input type="checkbox" id="cbxRemExt" /> Remember me
</section>
In Login view, add this:
<script>
// ** change to eq(1) (2 places!) if your social login form is the second form on the page,
// keep as below if first form is your social login form **
$("#cbxRemExt").change(function () {
var isChecked = $(this).is(":checked");
var actionstring = $('form').eq(0).attr('action');
actionstring = actionstring.replace('RemembermeExtnl=' + (isChecked ? 'f' : 't'), 'RemembermeExtnl=' + (isChecked ? 't' : 'f'))
$('form').eq(0).attr('action', actionstring);
});
</script>
In _ExternalLoginListPartial:
string action = Model.Action;
string returnUrl = Model.ReturnUrl;
string remembermeExtnl = Model.RemembermeExtnl;
using (Html.BeginForm(action, "Account", new { ReturnUrl = returnUrl, RemembermeExtnl = remembermeExtnl }))

ASP.net Identity Disable User

Using the new ASP.net Identity in MVC 5, How do we disable a user from logging in? I don't want to delete them, maybe just disable their account for a time period.
Does anyone have any ideas on this as I don't see a status column or anything on the ASPNetUsers table.
await userManager.SetLockoutEnabledAsync(applicationUser.Id, true);
await userManager.SetLockoutEndDateAsync(DateTime.Today.AddYears(10));
Update: As CountZero points out, if you're using v2.1+, then you should try and use the lockout functionality they added first, before trying the solution below. See their blog post for a full sample: http://blogs.msdn.com/b/webdev/archive/2014/08/05/announcing-rtm-of-asp-net-identity-2-1-0.aspx
Version 2.0 has the IUserLockoutStore interface that you can use to lockout users, but the downside is that there is no OOB functionality to actually leverage it beyond the pass-through methods exposed by the UserManager class. For instance, it would be nice if it would actually increment the lockout count as a part of the standard username/password verification process. However, it's fairly trivial to implement yourself.
Step #1: Create a custom user store that implements IUserLockoutStore.
// I'm specifying the TKey generic param here since we use int's for our DB keys
// you may need to customize this for your environment
public class MyUserStore : IUserLockoutStore<MyUser, int>
{
// IUserStore implementation here
public Task<DateTimeOffset> GetLockoutEndDateAsync(MyUser user)
{
//..
}
public Task SetLockoutEndDateAsync(MyUser user, DateTimeOffset lockoutEnd)
{
//..
}
public Task<int> IncrementAccessFailedCountAsync(MyUser user)
{
//..
}
public Task ResetAccessFailedCountAsync(MyUser user)
{
//..
}
public Task<int> GetAccessFailedCountAsync(MyUser user)
{
//..
}
public Task<bool> GetLockoutEnabledAsync(MyUser user)
{
//..
}
public Task SetLockoutEnabledAsync(MyUser user, bool enabled)
{
//..
}
}
Step #2: Instead of UserManager, use the following class in your login/logout actions, passing it an instance of your custom user store.
public class LockingUserManager<TUser, TKey> : UserManager<TUser, TKey>
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
{
private readonly IUserLockoutStore<TUser, TKey> _userLockoutStore;
public LockingUserManager(IUserLockoutStore<TUser, TKey> store)
: base(store)
{
if (store == null) throw new ArgumentNullException("store");
_userLockoutStore = store;
}
public override async Task<TUser> FindAsync(string userName, string password)
{
var user = await FindByNameAsync(userName);
if (user == null) return null;
var isUserLockedOut = await GetLockoutEnabled(user);
if (isUserLockedOut) return user;
var isPasswordValid = await CheckPasswordAsync(user, password);
if (isPasswordValid)
{
await _userLockoutStore.ResetAccessFailedCountAsync(user);
}
else
{
await IncrementAccessFailedCount(user);
user = null;
}
return user;
}
private async Task<bool> GetLockoutEnabled(TUser user)
{
var isLockoutEnabled = await _userLockoutStore.GetLockoutEnabledAsync(user);
if (isLockoutEnabled == false) return false;
var shouldRemoveLockout = DateTime.Now >= await _userLockoutStore.GetLockoutEndDateAsync(user);
if (shouldRemoveLockout)
{
await _userLockoutStore.ResetAccessFailedCountAsync(user);
await _userLockoutStore.SetLockoutEnabledAsync(user, false);
return false;
}
return true;
}
private async Task IncrementAccessFailedCount(TUser user)
{
var accessFailedCount = await _userLockoutStore.IncrementAccessFailedCountAsync(user);
var shouldLockoutUser = accessFailedCount > MaxFailedAccessAttemptsBeforeLockout;
if (shouldLockoutUser)
{
await _userLockoutStore.SetLockoutEnabledAsync(user, true);
var lockoutEndDate = new DateTimeOffset(DateTime.Now + DefaultAccountLockoutTimeSpan);
await _userLockoutStore.SetLockoutEndDateAsync(user, lockoutEndDate);
}
}
}
Example:
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> Login(string userName, string password)
{
var userManager = new LockingUserManager<MyUser, int>(new MyUserStore())
{
DefaultAccountLockoutTimeSpan = /* get from appSettings */,
MaxFailedAccessAttemptsBeforeLockout = /* get from appSettings */
};
var user = await userManager.FindAsync(userName, password);
if (user == null)
{
// bad username or password; take appropriate action
}
if (await _userManager.GetLockoutEnabledAsync(user.Id))
{
// user is locked out; take appropriate action
}
// username and password are good
// mark user as authenticated and redirect to post-login landing page
}
If you want to manually lock someone out, you can set whatever flag you're checking in MyUserStore.GetLockoutEnabledAsync().
You can have a new class, which should be derived from IdentityUser class. YOu can add a boolean property in the new class and can use this new property of take care per check for login process. I also done it pretty well. I might wanna take a look at : blog
UserManager.RemovePasswordAsync("userId") will effectively disable a user. If the user has no password he will not be able to log in. You will need to set a new password to enable the user again.

Resources