ASP.NET Core - Do not open the page by entering the address in the browser? - asp.net-core

I have three actions that open in stages... The actions are as follows?
https://localhost:44336/Login
https://localhost:44336/Register
https://localhost:44336/Active
First the login page opens, then the active page, then the registration page.
I used viewbag to prevent these URLs from being opened directly
public IActionResult Activate()
{
if (ViewBag.Mobile == null)
{
return RedirectToAction("Index", "Home");
}
return View();
}
This has a problem when I refresh the page out of action!
Is there a way to do this without a viewbag? If the user enters the address in the browser, the page will not open, but if it is refreshed, it will not leave the page?

I think your requirement can be solved by Autentication.I wrote a demo about cookie Authenticantion in asp.net core .you could also try other methods like jwt.
Codes in your startup class:
public void ConfigureServices(IServiceCollection services)
{
.......
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, m =>
{
m.LoginPath = new Microsoft.AspNetCore.Http.PathString("/Users/Login");
m.AccessDeniedPath = new Microsoft.AspNetCore.Http.PathString("/Home/Privacy");
m.SlidingExpiration = true;
});
.......
}
........
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
........
app.UseAuthentication();
app.UseAuthorization();
.......
}
Codes in your controller:
public async Task<IActionResult> Login(User user)
{
........
var claims = new List<Claim> { new Claim(ClaimTypes.Name,user.Name),new Claim("Mobile",user.Mobile)};
var userPricipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, userPricipal, new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(10),//you could set the time of cookie here
IsPersistent = false,
AllowRefresh = false
});
var mobile = _context.User.FirstOrDefault(m => m.Name == user.Name && m.Mobile == user.Mobile).Mobile;
if (user.Mobile != null)
{
return RedirectToAction(nameof(Index));
}
..........
return View();
}
Add the [Authorize] Attribute on the Action that needs Authentication:
[Authorize]
public async Task<IActionResult> Index()
{
return View();
}
you could read the offical document for more details:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0

Related

Force 2 Factor Authentication for new users when they login for the first time .NET Core

I am trying to configure the 2FA when users log in for the first time in .net core. So I added an if condition for checking if 2FA is not enabled then redirecting to creating MFA, however, a major flaw here is that the users can change the URL link on the browser to skip 2FA creation, how can I avoid this? Below are my Account Controller Codes:
Login Controller Methods
[HttpGet]
[AllowAnonymous]
public IActionResult Login(string? returnurl = null)
{
ViewData["ReturnUrl"] = returnurl;
return View();
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginViewModel model, string? returnurl = null)
{
ViewData["ReturnUrl"] = returnurl;
returnurl ??= Url.Content("~/");
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.UserName);
var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
if (user.TwoFactorEnabled==false)
{
return RedirectToAction(nameof(EnableAuthenticator), new { returnurl, model.RememberMe });
}
return LocalRedirect(returnurl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(VerifyAuthenticatorCode), new { returnurl, model.RememberMe });
}
if (result.IsLockedOut)
{
return View("Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
return View(model);
}
Enable 2FA Controller Methods
[HttpGet]
public async Task<IActionResult> EnableAuthenticator()
{
string AuthenticatorUriFormat = "MY-OTP-SECRET-HERE";
var user = await _userManager.GetUserAsync(User);
await _userManager.ResetAuthenticatorKeyAsync(user);
var token = await _userManager.GetAuthenticatorKeyAsync(user);
string AuthenticatorUri = string.Format(AuthenticatorUriFormat, _urlEncoder.Encode("My-App-Name-Here"),
_urlEncoder.Encode(user.Email), token);
var model = new MFAViewModel() { Token = token, QRCodeUrl = AuthenticatorUri };
return View(model);
}
[HttpPost]
public async Task<IActionResult> EnableAuthenticator(MFAViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.GetUserAsync(User);
var succeeded = await _userManager.VerifyTwoFactorTokenAsync(user, _userManager.Options.Tokens.AuthenticatorTokenProvider, model.Code);
if (succeeded)
{
await _userManager.SetTwoFactorEnabledAsync(user, true);
}
else
{
ModelState.AddModelError("Verify", "Your two factor auth code could not be validated.");
return View(model);
}
}
return RedirectToAction(nameof(AuthenticatorConfirmation));
}
As mentioned in my comment, you could consider configuring the application use Claims-based authorization, after user login with 2FA successfully, you could add a claim store the 2FA login result and add it to the login user. After that, in your application, create a policy which requires the claim, and add the Authorize attribute on each controller.
Besides, you could also add the user's claims after 2FA , then create a custom middleware/Authorize attribute to validate each request and check whether the current user contains the claims or not. You can refer to the following links: Custom Authorization attributes and How To Override Attribute Class To Do Custom Authorization In .NET Core.

User.Identity.IsAuthenticated is false although SignInManager.PasswordSignInAsync() succeeds

I did find similar questions but none of the provided answers helped me.
I did follow a tutorial to add Identity to my ASP.net Core 2.2 project (https://retifrav.github.io/blog/2018/03/20/csharp-dotnet-core-identity-mysql/)
Even though SignInManager.PasswordSignInAsync() succeeds, both User.Identity.IsAuthenticated and SignInManager.IsSignedIn(User) are false in the _Layout view.
_Layout.cshtml:
#using Microsoft.AspNetCore.Identity
#inject SignInManager<MySiteUser> SignInManager
#inject UserManager<MysiteUser> UserManager
......................
<div>
#if (SignInManager.IsSignedIn(User))
{
<div>Hello #UserManager.GetUserName(User)!</div>
}
#if (User.Identity.IsAuthenticated)
{
<div>User is authenticated </div>
}
</div>
In Startup.CS, in ConfigureServices I have:
services.AddIdentity<MySiteUSer, MySiteRole>().AddEntityFrameworkStores<IdentityContext>().AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.User.RequireUniqueEmail = true;
});
services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/Login");
services.AddMvc();
In Startup.CS, in Configure() I have:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider services)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication(); ;
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
In AccountController I have:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
var user = await _signInManager.UserManager.FindByNameAsync(model.Username);
var userPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
var identity = userPrincipal.Identity;
if(identity.IsAuthenticated)
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
MySiteUser and MySiteRole just override default Identity classes
public class MySiteUser : IdentityUser<int>
{
}
public class MySiteRole : IdentityRole<int>
{
}
Edit:
Because all replies are about the Controller, before this one I used the following code in AccountController
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
The result was the same.
I've just find a solution, although I don't understand why it works. I've checked Chrome Developer Tools to see if the authentication cookie is set, and it wasn't.
After I deleted all cookies for the site, the app set the cookie and all works well.
I also tested the simpler AccountController and that works fine, too:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var result = await _signInManager
.PasswordSignInAsync(model.Username, model.Password,
Model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
You don't need to check if(identity.IsAuthenticated) before redirecting user to home page. If the code reaches the (result.Succeeded) block, it means user is authenticated. if(identity.IsAuthenticated) will be true only at the end of the request, meaning you have returned an HttpResponse (or something) that will create a cookie on client side.
So the solution is to remove the condition inside the Login action.
/*if(identity.IsAuthenticated) <===== remove this */
return RedirectToAction("Index", "Home");
EDITS: Don't forget that when you use RedirectToAction, it's still a same request so the cookie will not be created yet. Instead, create and use a temporary success view as I usually do.
/*if(identity.IsAuthenticated) <===== remove this */
return RedirectToAction("Index", "Home"); <===== remove this */
/*put url in viewbag or use returnUrl variable*/
#ViewBag.ReturnUrl = Url.Action("index","home"/*,null,Request.Url.Scheme*/);
return View("LoginSuccess");
and here is what to put in your LoginSuccess.cshtml view
<h2>Authenticated. Wait 2 seconds or click continue</h2>
<p><a href="#ViewBag.ReturnUrl">
Continue</a></p>
<script>
setTimeout(function () {
window.location = "#ViewBag.ReturnUrl";
}, 2000)
</script>
PS: You may need to use partial view to avoid conflict with your layout page headers... return PartialView("LoginSuccess");
I believe I have stumbled on this answer to this. First try this in IE, it may work there. Apparently Chrome is quite fussy regarding the cookie that is used for the Authentication unless you are talking to it in HTTPS. If you are in development and using HTTP and using Chrome the IsSignedIn(User) may fail. This is unbelievably annoying and quite a productivity sink. HTH

How to know if the user is Already SignedIn and thus prevent displaying the Login page in asp dot net core 2.0 Cookie based authentication?

This is the basic code snippet !!
Show me the Login page if none of the user is signedIn. If any user is active it should redirect to Index Page of Home Controller and should not show Login page !!
public class AccountController : CommonController
{
public AccountController(IOptions<ConnectionSetting> connString) : base(connString)
{
}
public IActionResult Index()
{
return RedirectToAction("Index", "Home");
}
[HttpGet]
[AllowAnonymous]
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login(User user)
{
if (ValidateUser(user.UserName , user.Password))
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name , user.UserName)
};
//Now Create an ClaimIdentity
var claimsIdentity = new ClaimsIdentity(claims, "BloggingCookie");
//Create Claim Principal using that Identity
ClaimsPrincipal principal = new ClaimsPrincipal(claimsIdentity);
await HttpContext.SignInAsync("BloggingCookie", principal );
return RedirectToAction("Index", "Home");
}
else
{
ViewData["Status"] = "Invalid Username or Password";
return View();
}
}
}
All you need is to add something like the following to the top of your login action:
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Manage");
}
You can of course redirect to wherever you like.

.net Core Authentication

I wanted to implement forms authentication with membership in my asp.net MVC Core application.
We had forms authentication setup in our previous application as below and wanted to use the same in .net core.
[HttpPost]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (!this.ModelState.IsValid)
{
return this.View(model);
}
//Authenticate
if (!Membership.ValidateUser(model.UserName, model.Password))
{
this.ModelState.AddModelError(string.Empty, "The user name or
password provided is incorrect.");
return this.View(model);
}
else
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return this.RedirectToAction("Index", "Home");
}
return this.View(model);
}
In my config:
<membership defaultProvider="ADMembership">
<providers>
<add name="ADMembership"
type="System.Web.Security.ActiveDirectoryMembershipProvider"
connectionStringName="ADConnectionString"
attributeMapUsername="sAMAccountName" />
</providers>
</membership>
So we are using active directory here in membership.
Is this still applicable in .net core.
If not what else is available in .net core for forms authentication and AD.
Would appreciate inputs.
Yes you can do that in Core MVC application. You enable form authentication and use LDAP as user store at the back-end.
Here is how I set things up, to give you start:
Startup.cs
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
// Read LDAP settings from appsettings
services.Configure<LdapConfig>(this.Configuration.GetSection("ldap"));
// Define an interface for authentication service,
// We used Novell.Directory.Ldap as implementation.
services.AddScoped<IAuthenticationService, LdapAuthenticationService>();
// Global filter is enabled to protect the whole site
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
...
});
// Form authentication and cookies settings
var cookiesConfig = this.Configuration.GetSection("cookies").Get<CookiesConfig>();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = cookiesConfig.CookieName;
options.LoginPath = cookiesConfig.LoginPath;
options.LogoutPath = cookiesConfig.LogoutPath;
options.AccessDeniedPath = cookiesConfig.AccessDeniedPath;
options.ReturnUrlParameter = cookiesConfig.ReturnUrlParameter;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Redirects all HTTP requests to HTTPS
if (env.IsProduction())
{
app.UseRewriter(new RewriteOptions()
.AddRedirectToHttpsPermanent());
}
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
}
app.UseStaticFiles();
app.UseStatusCodePagesWithReExecute("/error", "?code={0}");
app.UseAuthentication();
app.UseMvc(routes =>
{
...
});
}
}
appsettings.json
{
"connectionStrings": {
"appDbConnection": xxx
},
"ldap": {
"url": "xxx.loc",
"bindDn": "CN=Users,DC=xxx,DC=loc",
"username": "xxx",
"password": "xxx",
"searchBase": "DC=xxx,DC=loc",
"searchFilter": "(&(objectClass=user)(objectClass=person)(sAMAccountName={0}))"
},
"cookies": {
"cookieName": "xxx",
"loginPath": "/account/login",
"logoutPath": "/account/logout",
"accessDeniedPath": "/account/accessDenied",
"returnUrlParameter": "returnUrl"
}
}
IAuthenticationService.cs
namespace DL.SO.Services.Core
{
public interface IAuthenticationService
{
IAppUser Login(string username, string password);
}
}
LdapAuthenticationService.cs
Ldap implementation of authentication service, using Novell.Directory.Ldap library to talk to active directory. You can Nuget that library.
using Microsoft.Extensions.Options;
using Novell.Directory.Ldap;
...
using DL.SO.Services.Core;
namespace DL.SO.Services.Security.Ldap
{
public class LdapAuthenticationService : IAuthenticationService
{
private const string MemberOfAttribute = "memberOf";
private const string DisplayNameAttribute = "displayName";
private const string SAMAccountNameAttribute = "sAMAccountName";
private const string MailAttribute = "mail";
private readonly LdapConfig _config;
private readonly LdapConnection _connection;
public LdapAuthenticationService(IOptions<LdapConfig> configAccessor)
{
// Config from appsettings, injected through the pipeline
_config = configAccessor.Value;
_connection = new LdapConnection();
}
public IAppUser Login(string username, string password)
{
_connection.Connect(_config.Url, LdapConnection.DEFAULT_PORT);
_connection.Bind(_config.Username, _config.Password);
var searchFilter = String.Format(_config.SearchFilter, username);
var result = _connection.Search(_config.SearchBase, LdapConnection.SCOPE_SUB, searchFilter,
new[] { MemberOfAttribute, DisplayNameAttribute, SAMAccountNameAttribute, MailAttribute }, false);
try
{
var user = result.next();
if (user != null)
{
_connection.Bind(user.DN, password);
if (_connection.Bound)
{
var accountNameAttr = user.getAttribute(SAMAccountNameAttribute);
if (accountNameAttr == null)
{
throw new Exception("Your account is missing the account name.");
}
var displayNameAttr = user.getAttribute(DisplayNameAttribute);
if (displayNameAttr == null)
{
throw new Exception("Your account is missing the display name.");
}
var emailAttr = user.getAttribute(MailAttribute);
if (emailAttr == null)
{
throw new Exception("Your account is missing an email.");
}
var memberAttr = user.getAttribute(MemberOfAttribute);
if (memberAttr == null)
{
throw new Exception("Your account is missing roles.");
}
return new AppUser
{
DisplayName = displayNameAttr.StringValue,
Username = accountNameAttr.StringValue,
Email = emailAttr.StringValue,
Roles = memberAttr.StringValueArray
.Select(x => GetGroup(x))
.Where(x => x != null)
.Distinct()
.ToArray()
};
}
}
}
finally
{
_connection.Disconnect();
}
return null;
}
}
}
AccountController.cs
Then finally after the user is verified, you need to construct the principal from the user claims for sign in process, which would generate the cookie behind the scene.
public class AccountController : Controller
{
private readonly IAuthenticationService _authService;
public AccountController(IAuthenticationService authService)
{
_authService = authService;
}
...
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (ModelState.Valid)
{
try
{
var user = _authService.Login(model.Username, model.Password);
if (user != null)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Username),
new Claim(CustomClaimTypes.DisplayName, user.DisplayName),
new Claim(ClaimTypes.Email, user.Email)
}
// Roles
foreach (var role in user.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
// Construct Principal
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, _authService.GetType().Name));
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
principal,
new AuthenticationProperties
{
IsPersistent = model.RememberMe
}
);
return Redirect(Url.IsLocalUrl(model.ReturnUrl)
? model.ReturnUrl
: "/");
}
ModelState.AddModelError("", #"Your username or password is incorrect.");
}
catch(Exception ex)
{
ModelState.AddModelError("", ex.Message);
}
}
return View(model);
}
}
Would this post help you integrate with AD for Authentication and Authorization?
MVC Core How to force / set global authorization for all actions?
The idea is add authentication within ConfigureServices method in Startup.cs file:
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireRole([Your AD security group name in here without domain name]) // This line adds authorization to users in the AD group only
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
In Asp.Net Core the Authentication is controlled through project properties.
Open the solution. Right click on the Project and Click Properties.
Click the Debug tab. Check the Enable Windows Authentication checkbox. Ensure Anonymous Authentication is disabled.
Here is Microsoft's document, https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth
Cheers!

MVC5 Authentication cannot save UseCookieAuthentication

I have a problem with MVC 5 Authentication
After success login, i think i cannot save UseCookieAuthentication.
My Startup code:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/MyAccount/Login")
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
}
My login controler:
public ActionResult Login(Login l, string ReturnUrl="")
{
using (CBDB2012Entities dc = new CBDB2012Entities())
{
var user = dc.Users.Where(a => a.UserName.Equals(l.Username) && a.Password.Equals(l.Password)).FirstOrDefault();
if (user != null)
{
FormsAuthentication.SetAuthCookie(user.UserName, l.RememberMe);
if (Url.IsLocalUrl(ReturnUrl))
{
return Redirect(ReturnUrl);
}
else
{
return RedirectToAction("MyProfile", "MyAccount");
}
}
}
ModelState.Remove("Password");
return View();
}
after login, it RedirectToAction("MyProfile", "MyAccount");
But in MyProfile view, i cannot see anything about user and i cann't access to contac page with [Authorize]:
-Contact controler
[Authorize]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
I has test via session in login control, and it work fine:
var user = dc.Users.Where(a => a.UserName.Equals(l.Username) && a.Password.Equals(l.Password)).FirstOrDefault();
if (user != null)
{
FormsAuthentication.SetAuthCookie(user.UserName, l.RememberMe);
Session["loginname"] = user.Employee.FirstName;
if (Url.IsLocalUrl(ReturnUrl))
{
return Redirect(ReturnUrl);
}
else
{
return RedirectToAction("MyProfile", "MyAccount");
}
}
i can get Session["loginname"] in view from database.