Prevent login with one user in different browser .NET Core - c#

I want to create a login page that contain user and password with ASP.NET Core 5.
How can I prevent login with one user in different browser?

I would suggest at login you create a unique token that is stored on the server and passed back to the client as either an encrypted cookie value or if you are using Claims authentication as a claim. You then write a filter to test that the sent token matches the server side stored value on Authentication. You may want to store and update a datetime on the token - similar to a sliding cache item - so that you can expire the token.
When another browser or login occurs and the token is within a certain date then you can either suppress the login attempt or generate a new token which would logout the first browser user.
An example filter is shown below.
public class SignOnCheckerFilter : IAuthorizationFilter
{
public SignOnCheckerFilter (){}
public void OnAuthorization(AuthorizationFilterContext context)
{
bool checkOk = false;
if (context.HttpContext.User.Identity.IsAuthenticated)
{
//read cookie or claims
//check match
//if match success
checkOk = true;
}
//return if checkOk
if(checkOk) return;
//return forbidden result if invalid match (you could redirect the user to a logout page?)
context.Result = new ForbidResult();
}
}

Related

How to Invalidate AspNetCore.Identity.Application Cookie after user log out

I am having trouble invalidating .AspNetCore.Identity.Application cookie in ASP.NET Core Identity once the user log out.
Once user clicks on log out below code will execute.
public async Task<IActionResult> Logout(LogoutInputModel model)
{
// build a model so the logged out page knows what to display
LoggedOutViewModel loggedOutViewModel = await BuildLoggedOutViewModelAsync(model.LogoutId);
_logger.LogInformation($"loggedOutViewModel : {JsonConvert.SerializeObject(loggedOutViewModel)}");
if (User?.Identity.IsAuthenticated == true)
{
// delete local authentication cookie
await _norskTakstSignInManager.SignOutAsync();
//clear cookies
var appCookies = Request.Cookies.Keys;
foreach (var cookie in appCookies)
{
Response.Cookies.Delete(cookie);
}
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
// check if we need to trigger sign-out at an upstream identity provider
if (loggedOutViewModel.TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
string url = Url.Action("Logout", new { logoutId = loggedOutViewModel.LogoutId });
// this triggers a redirect to the external provider for sign-out
return SignOut(new AuthenticationProperties { RedirectUri = url }, loggedOutViewModel.ExternalAuthenticationScheme);
}
return View("LoggedOut", loggedOutViewModel);
}
This successfully clears all the cookies in the browser, however, if I grab the value of the cookie named ".AspNetCore.Identity.Application" prior to signing out, then add it back in on to the browser, then i can log in to the application without entering user credentials.
I tested few flows setting up cookie expiration time in different ways but non of them seem to work correctly.
I want to know way to invalidate the cookie without just clearing to resolve this issue.Then user should not be able to enter cookie manually and log in to the system. Any help is hugly appreciated. Thank you.
That's by design... one thing you can do is try updating the user's security stamp after logout, using UserManager.UpdateSecurityStampAsync.
This way the cookie's security stamp won't match the one in the database and the cookie will no longer be valid (however, no other cookie issued to that user will, even if they haven't "signed out"... so if a user has several sessions opened, all of those cookies will stop being valid, not just the one you signed out).
Identity doesn't track specific user sessions (it just validates the cookie against the user, and if it matches, it matches). If you want to be able to selectively remove sessions, you'll have to track them yourself
For me the best security practice is save every login and logout in one record with an unique random ID as GUID, then save this "id session" into the claims, and check this everytime the user access, if the ID in the claim is correct to that session.

Authentication/Authorization in ASP.NET Core using Enterprise SingleSignon page and Microsoft SignInManager

Presently I am working on an authentication issue in one of my ASP.NET Core(3.0) application.
To give some background, we have to use an Enterprise Single sign on page to authenticate the users. Once the user got authenticated it redirects back to our application along with user name in a HTTP header called "SM_USER". Then using that information we load corresponding Claim information from DB using Microsoft SignInManger. The application is working fine if the user is accessing the root but it couldn't able to access if they are trying to navigate a specific page directly, like http://website/Controller/Index.
I am suspecting that we may have implemented it wrongly so would like to know how should the below scenario to be implemented?
Our users first get authenticated using an enterprise Login Page(Single sign on) then redirected to our application. user information available on HTTP Headers and the corresponding claims information is available in our DB which we need to use for authorization purpose(we wanted to use Microrosoft SignInManger to load them).
I found the issue after researching it throughly so sharing here in case it useful for someone.
we observed whenever a user try to access a page if the user is not authenticated then it is redirecting Login page(Observed that decorating them with [Authorize] attribute is causing this), where We are using this Login page for local development purpose.
so when the user get redirected to login page and if the environment is not development then below code is executed on the Get method, which takes care of signing the user and creating UserPrincipal. Then after that we redirecting to the page the user requested.
if (!_signInManager.IsSignedIn(User))
{
string userName = HttpContext.Request.Headers["SM_USER"].ToString();
if (userName.Length > 0)
{
var user = await _userManager.FindByNameAsync(userName);
if (user != null)
{
var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
await _signInManager.Context.SignInAsync(IdentityConstants.ApplicationScheme,
claimsPrincipal,
new AuthenticationProperties { IsPersistent = true });
if (string.IsNullOrEmpty(returnUrl)) //returnUrl is a parameter get passed by the system.
{
return RedirectToAction("<Action>", "<Controller>");
}
else
{
return Redirect(returnUrl);
}
}
}
}

Additionally password protect an ASP.NET Core MVC website hosted as Azure WebApp with existing authentication

I have an existing ASP.NET Core MVC application with ASP.NET Core Identity where I use a combination of signInManager.PasswordSignInAsync and [Authorize] attributes to enforce that a user is logged in to website, has a certain role et cetera. This works fine locally and in an Azure WebApp.
Now, I want to publish a preview version of my application to another Azure WebApp. This time, I want each visitor to enter another set of credentials before anything from the website is being shown. I guess I'd like to have something like an .htaccess / BasicAuthenication equivalent. However, after a user entered the first set of credentials, he should not be logged in since he should need to use the normal login prodecure (just as in the live version which is publicly accessible but this has certain pages which require the user to be logged in). Basically, I just want to add another layer of password protection on top without impacting the currently existing authentication.
Given that I want allow access to anyone with the preview password, the following solutions do not seem to work in my case:
Limit the access to the WebApp as a firewall setting. The client IPs will not be from a certain IP range and they will be dynamically assigned by their ISP.
Use an individual user account with Azure AD in front. This might be my fallback (although I'm not sure on how to implement exactly) but I'd rather not have another set of individual user credentials to take care. The credentials could even be something as simple as preview // preview.
Is there a simple way like adding two lines of codes in the Startup class to achieve my desired second level of password protection?
You can do a second auth via a basic auth, something simple and not too much code. You will need a middleware that will intercept/called after the original authentication is done
Middleware
public class SecondaryBasicAuthenticationMiddleware : IMiddleware
{
//CHANGE THIS TO SOMETHING STRONGER SO BRUTE FORCE ATTEMPTS CAN BE AVOIDED
private const string UserName = "TestUser1";
private const string Password = "TestPassword1";
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
//Only do the secondary auth if the user is already authenticated
if (!context.User.Identity.IsAuthenticated)
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic "))
{
// Get the encoded username and password
var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();
// Decode from Base64 to string
var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));
// Split username and password
var username = decodedUsernamePassword.Split(':', 2)[0];
var password = decodedUsernamePassword.Split(':', 2)[1];
// Check if login is correct
if (IsAuthorized(username, password))
{
await next.Invoke(context);
return;
}
}
// Return authentication type (causes browser to show login dialog)
context.Response.Headers["WWW-Authenticate"] = "Basic";
// Return unauthorized
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else
{
await next.Invoke(context);
}
}
//If you have a db another source you want to check then you can do that here
private bool IsAuthorized(string username, string password) =>
UserName == username && Password == password;
}
In startup -> Configure (make sure you add this after your existing authentication and authorization)
//Enable Swagger and SwaggerUI
app.UseMiddleware<SecondaryBasicAuthenticationMiddleware>(); //can turn this into an extension if you wish
app.UseAuthentication();
app.UseAuthorization();
In Startup -> ConfigureServices register the middleware
services.AddTransient<SecondaryBasicAuthenticationMiddleware>();
And chrome should pop up a basic auth dialog like this

Writing Cookie on successful submit in Blazor Application

I have a login page that allows the user to login. In the HandlieValidSubmit() event I check if username and password matches with the value stored in database.
If everything is fine I want to store some data into the usercookie before redirecting to another site. This is the html/blazor-Code:
<EditForm>
<!-- standard form-controls here -->
</EditForm>
#code {
private Models.LoginUser _loginUser = new Models.LoginUser();
private EditContext _editContext;
private void HandleValidSubmit()
{
if (UserApi.Login(_loginUser.Mail, _loginUser.Password, out string error, out Guid? guid))
{
NaviationManager.NavigateTo($"/manage/{guid}");
}
}
}
the Cookie is set from within the Login-Function and looks like this:
public DateTime SetCookie<T>(T data, TimeSpan expiration, bool httpOnly = true, bool secure = true)
{
DateTime expireDate = DateTime.Now.Add(expiration);
if (data == null) return DateTime.Now;
Type dataType = typeof(T);
var response = _httpContextAccessor.HttpContext.Response;
CookieOptions cookieOptions = new CookieOptions
{
HttpOnly = httpOnly,
Secure = secure,
Expires = expireDate
};
foreach (var property in dataType.GetProperties())
{
var storeInCookieAttribute = property.GetCustomAttribute<StoreInCookieAttribute>();
if (storeInCookieAttribute == null) continue;
response.Cookies.Append(BuildCookieKey(dataType.Name, property.Name), property.GetValue(data) as string, cookieOptions);
}
return expireDate;
}
IMHO this is the standard "how-to-write-cookies-in-netcore" - way.
When I try to write the cookie I receive the error:
"The response headers cannot be modified because the response has already started."
I understand what this error wants to tell me. Alas I do not really know how to prevent this. I expected that at this point the response should not have started at all.
Is there another event than HandleValidSubmit() I need to use instead? Or can I just clear the Response before writing the cookie without bad side effects?
Blazor Server App is websocket-based application, not HTTP-based one, so the HttpContext service is not available.
When you create a Blazor Server App with support for IdentityUI, you get in the default template a component ( AuthorizeView ) that enables login and logout. When you click on the "Login" button, you are being redirected to a Login page where you can enter your credentials. The Login page is actually a Razor Page, not part of the Blazor App, meaning that you are no longer in the realm of Blazor, and here in this new realm (The Razor Page), the HttpContext is available, you don't even have to use the HttpContextAccessor, as the HttpContext is provided as a property in the PageModel object. After the user has been logged in, cookies created, etc., he is redirected to Blazor.
This is how you can do it. Just emulate this procedure... Create a Razor Page, where you can do all that stuff. You may also pass a return url, so that you'll be redirected to a specific Component page instead of the the Index Component Page.
Note: HandleValidSubmit() is a method that is called if your forms component elements have passed validation. It has got nothing to do with the issue in question.
Note: To use the NavigationManger.NavigateTo method to navigate to external location (outside of the Blazor App realm), add a second boolean parameter with the value true.
Note: This may help you how to cope . There are also many answers related to the current subject and authentication with OpenID Connect, Okta, IdentityUI, etc. Just look for them if you are in need.
Note: Are you aware that you can store data in the local storage and session storage ?
Hope this helps...

Combine server-side and client-side authentication with WebAPI

I have a legacy ASP.NET webforms application in which users login via a form that is processed server-side. If the entered username + password match to credentials in the database, I set some values in the sessions (e.g., the current user ID) and perform a Response.Redirect afterwards. I'm also creating a HttpCookie for a "automatically relog me next time I visit" functionality.
Currently, I'm also adding WebApi support into that web application. I've managed to implement token authentication which allows me to login on the client side.
How can I combine both authentication approaches? I want to the user to enter his credentials once, get authenticated on the server side and on the client side an redirect the users to another page after authenticating.
The following code will create a cookie to keep user logged in.
// login etc
if (chkRemember.Checked)
{
// calculate the total number of minutes in 20 days to use as the time out.
int timeout = (int)TimeSpan.FromDays(30).TotalMinutes;
// create an authentication ticket
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(txtUserName.Text, true, timeout);
// Encrypt the ticket
string encrptedTicked = FormsAuthentication.Encrypt(ticket);
// create the cookie for the ticket, and put the ticket inside
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encrptedTicked);
// give cookie and ticket same expiration
cookie.Expires = ticket.Expiration;
// Attach cookie to current response. it will now to the client and then back to the webserver with every request
HttpContext.Current.Response.Cookies.Set(cookie);
// send the user to the originally requested page.
string requestedPage = FormsAuthentication.GetRedirectUrl(txtUserName.Text, false);
Response.Redirect(requestedPage, true);
}
else
{
// login without saving cookie to client
FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, false);
}
You can use token based authentication in webapi using Angular JS. Visit following link
http://www.dotnetcurry.com/aspnet/1223/secure-aspnet-web-api-using-tokens-owin-angularjs

Categories