asp.net core 2.0 cookie authentication with session variable - asp.net-core-mvc-2.0

I'm trying to implement asp.net core cookie-based authentication. So I have added the code below to my startup.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.AccessDeniedPath = new PathString("/login");
options.LoginPath = new PathString("/login");
options.SlidingExpiration = true;
});
and I'm sign-in using the code below
[HttpPost]
[ValidateAntiForgeryToken]
[Route("login")]
public async Task<IActionResult> Login(AuthViewModel authView)
{
if (ModelState.IsValid)
{
var (status, message, SigninUser) = await authentication.Authenticate(new User()
{
email = authView.Email,
pwd = authView.Password
});
if (status)
{
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "App Member"),
new Claim(ClaimTypes.Email, SigninUser.email)
};
ClaimsIdentity identity = new ClaimsIdentity(claims, "cookie");
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(
scheme: CookieAuthenticationDefaults.AuthenticationScheme,
principal: principal,
properties: new AuthenticationProperties
{
IsPersistent = authView.RememberMe,
ExpiresUtc = DateTime.UtcNow.AddYears(1)
});
HttpContext.Session.Set<User>("session_user", SigninUser);
if (Url.IsLocalUrl(authView.returnUrl))
return Redirect(authView.returnUrl);
else
return RedirectToAction("Index");
}
else
{
authView.Status = false;
authView.Message = message;
}
}
else
{
string message = string.Join(" | ", ModelState.Values.SelectMany(e => e.Errors).Select(v => v.ErrorMessage));
authView.Status = false;
authView.Message = message;
}
return View(authView);
}
This works fine. But when I keep the browser IDLE for like 30 minutes the "session_user" session variable get expired and the user still gets authenticated. How can I resolve this?
Also using cookie-based authentication can have a performance penalty?
Thanks

Related

HttpContext.Session.GetInt32() returns null

I have a simple Login form, where I set user Session data and redirect to Home page. This worked well before, but now in HomeController/Index, sessionID returns null and I'm not sure what caused this. It finds, that user is Authenticated, but doesn't get its ID.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel user)
{
if (ModelState.IsValid)
{
user.Password = Encryption.Encrypt(user.Password);
var checkLogin = _context.Users.FirstOrDefault(x => x.UserName.Equals(user.UserName) && x.Password.Equals(user.Password));
if (checkLogin != null)
{
HttpContext.Session.SetInt32("ID", checkLogin.ID);
HttpContext.Session.SetString("Name", checkLogin.Name);
HttpContext.Session.SetString("Surname", checkLogin.Surname);
HttpContext.Session.SetString("Role", checkLogin.Role.ToString());
var claims = new Claim[]
{
new Claim(ClaimTypes.Name, checkLogin.Name),
new Claim(ClaimTypes.Role, checkLogin.Role.ToString())
};
var identity = new ClaimsIdentity(claims, "AuthenticationCookie");
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(identity);
//var authProperties = new AuthenticationProperties()
//{
// IsPersistent = true
//};
await HttpContext.SignInAsync("AuthenticationCookie", claimsPrincipal);
return RedirectToAction("Index", "Home");
}
else
{
ViewBag.Notification = " Wrong Username or Password";
}
}
return View();
}
Home:
[HttpGet]
public IActionResult Index()
{
var authenticated = HttpContext.User.Identity.IsAuthenticated;
if (authenticated)
{
int? sessionID = HttpContext.Session.GetInt32("ID");
int userCode = _context.Users.First(x => x.ID == sessionID.GetValueOrDefault()).UserCode;
if(userCode != 0)
{
_usersHelper.CheckForTemporaryManagers(userCode);
}
}
return View();
}
My Startup has all the required info:
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
string sessionId = Configuration.GetConnectionString("EndSequance");
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.IsEssential = true;
options.Cookie.Name = sessionId;
options.Cookie.Path = "/" + sessionId;
});
services.AddHttpContextAccessor();

IdentityServer4 and external oauth privider: The oauth state was missing or invalid

I'm trying to implement external oauth authentication within IdentityServer4.
All auth requests goes successfully. I have a message AuthenticationScheme: Identity.External signed in. in app log.
But when authentication process tries to go back to ExternalLoginCallback action it falls with Error 500 (The oauth state was missing or invalid) after HTTP 302.
Screenshot is here
Result 302. Request https://localhost:5999/Account/ExternalLoginCallback?state=xxx&code=xxx&session_state=xxx
Then request goes to https://localhost:5999/Account/ExternalLoginCallback (without any parametres)
My IS4 Startup
IdentityServerConfiguration.AddIdentityServer(services, _configuration);
services
.AddAuthentication()
.AddTinkoff(_configuration)
.AddSber(_configuration)
.AddEsia(_configuration);
AddTinkoff extension method:
public static AuthenticationBuilder AddTinkoff(this AuthenticationBuilder builder, IConfiguration config)
{
return builder.AddOAuth("TinkoffId", "Tinkoff ID", options =>
{
options.AuthorizationEndpoint = "https://id.tinkoff.ru/auth/authorize?response_type=code";
options.TokenEndpoint = "https://id.tinkoff.ru/auth/token?grant_type=authorization_code";
options.UserInformationEndpoint = "https://id.tinkoff.ru/userinfo/userinfo";
options.CallbackPath = "/Account/ExternalLoginCallback";
options.ClientId = "xxx";
options.ClientSecret = "xxxx";
options.SaveTokens = true;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.BackchannelHttpHandler = new TinkoffAuthorizingHandler(new HttpClientHandler(), options);
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "name");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email", ClaimValueTypes.Email);
options.ClaimActions.MapAll();
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Post, context.Options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
request.Headers.Add(
HttpRequestHeader.ContentType.ToString(),
"application/x-www-form-urlencoded"
);
request.Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>() {
new KeyValuePair<string, string>("client_id",options.ClientId),
new KeyValuePair<string, string>("client_secret",options.ClientSecret)
});
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user.RootElement);
},
//OnTicketReceived = async context =>
//{
// context.HttpContext.User = context.Principal;
// //context.SkipHandler();
//}
};
//options.Scope.Add("profile");
//options.Scope.Add("email");
//options.Scope.Add("phone");
});
}
My ExternalLogin action:
[HttpPost]
[HttpGet]
[AllowAnonymous]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
_logger.LogInformation($"External login fired. ReturnUrl='{returnUrl}'. Provider='{provider}'");
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
ExternalLoginCallback action not fired while debug.
What I'm doing wrong. Why request redirects to itself without params?
Thank you.
I solved an issue for me.
I added
OnTicketReceived = async context =>
{
context.HttpContext.User = context.Principal;
//context.SkipHandler();
context.ReturnUri += $"/{context.Request.QueryString}";
}
Now it works as expected

skipped queue in MassTransit with RabbitMQ in dot net core application

I have three projects. One is Dot net core MVC, two are API projects. MVC is calling one API for user details. When user details are asked, I am sending message to queue through MassTransit. I am seeing skipped queue. There's consumer in third project which is API project.
I tried to make another solution for a demo with same configuration. It's running fine.
Below is MVC Razor page code..
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
if (ModelState.IsValid)
{
var user = await AuthenticateUser(Input.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
#region snippet1
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Email),
new Claim("FullName", user.FullName),
new Claim(ClaimTypes.Role, "Administrator"),
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(15),
IsPersistent = true,
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
#endregion
_logger.LogInformation("User {Email} logged in at {Time}.",
user.Email, DateTime.UtcNow);
return LocalRedirect(Url.GetLocalUrl(returnUrl));
}
return Page();
}
private async Task<ApplicationUser> AuthenticateUser(string email)
{
if (!string.IsNullOrEmpty(email))
{
using (var client = new System.Net.Http.HttpClient())
{
var request = new System.Net.Http.HttpRequestMessage();
request.RequestUri = new Uri("http://localhost:52043/api/user?uName=" + email); // ASP.NET 3 (VS 2019 only)
var response = await client.SendAsync(request);
var customer = Newtonsoft.Json.JsonConvert.DeserializeObject<Customers>(response.Content.ReadAsStringAsync().Result);
return new ApplicationUser()
{
Email = email,
FullName = customer.FullName
};
}
}
else
{
return null;
}
}
MVC Startup:
services.AddMassTransit(x =>
{
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
// configure health checks for this bus instance
cfg.UseHealthCheck(provider);
cfg.Host("rabbitmq://localhost");
}));
});
services.AddMassTransitHostedService();
User API Code - 52043:
[HttpGet]
public async Task<IActionResult> Get(string uName)
{
var customer = _userRepository.GetCustomerByUserName(uName);
Uri uri = new Uri("rabbitmq://localhost/loginqueue");
var endpoint = await _bus.GetSendEndpoint(uri);
await endpoint.Send(new LoginObj() { NoteString = customer.FullName + " has logged in at " + DateTime.Now.ToString() });
return Json(customer);
}
Logging API - Consumer Code:
public class LoginConsumer : IConsumer<LoginObj>
{
private readonly ILogger<object> _logger;
public LoginConsumer(ILogger<object> logger)
{
_logger = logger;
}
public async Task Consume(ConsumeContext<LoginObj> context)
{
var data = context.Message;
_logger.LogInformation(data.ToString());
}
}
Login API Startup:
services.AddMassTransit(x =>
{
x.AddConsumer<LoginConsumer>();
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
// configure health checks for this bus instance
cfg.UseHealthCheck(provider);
cfg.Host("rabbitmq://localhost");
cfg.ReceiveEndpoint("loginqueue", ep =>
{
ep.PrefetchCount = 16;
ep.UseMessageRetry(r => r.Interval(2, 100));
ep.ConfigureConsumer<LoginConsumer>(provider);
});
}));
});
services.AddMassTransitHostedService();
As per the documentation:
MassTransit uses the full type name, including the namespace, for message contracts. When creating the same message type in two separate projects, the namespaces must match or the message will not be consumed.
Make sure that your message type has the same namespace/type in each project.

O365 login is not working after log out in MVC

There is some problem in my code not sure where I am getting wrong. Earlier same code used to work properly. Now I am trying to log in to application in both the ways i.e through ClaimPrincipal and Claim Identity. In both the ways Sometime data is null. Not sure where is the issue.
Below is my code
StartUp.cs
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Authority = authority,
RedirectUri = redirectUri,
//PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.IdToken,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false // This is a simplification
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed
}
}
);
SignIn Method
[AllowAnonymous]
public void SignIn(string ReturnUrl = "/", string loginType = "")
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/Account/Office365LoginCallback" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
RedirectURL Code
[AllowAnonymous]
[ExceptionHandler]
public async Task<ActionResult> Office365LoginCallback(string code)
{
var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
string userName = userClaims?.FindFirst("name")?.Value;
string userEmail = userClaims?.FindFirst("preferred_username")?.Value;
string userId = userClaims?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
ViewBag.TenantId = userClaims?.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid")?.Value;
return Redirect("~/");
}
catch (Exception ex)
{
throw ex;
}
}
SignOut Method
public ActionResult LogOff()
{
//AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
//HttpContext.GetOwinContext().Authentication.SignOut();
FormsAuthentication.SignOut();
//HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
//HttpContext.Session[AppConstants.UserEmail] = null;
//HttpContext.Session[AppConstants.UserUpload] = null;
//HttpContext.Session[AppConstants.UserImage] = null;
//HttpContext.Session[AppConstants.CurrentRole] = null;
//HttpContext.Session[AppConstants.Users] = null;
//Session.Clear();
//Session.Abandon();
return RedirectToAction("Login", "Account");
}
Any Help will be appreciated. FYI I have tried same code in new project there is working fine but here in my old application its not working once clicked on Logout

ASP.NET Identity 2.1 - Password Reset Invalid Tokens

ASP.NET Identity is returning an 'Invalid token.' response when resetting a password for users.
I've tried the following:
URL Encode the code before sending email
URL Encode & Decode the code before and after
Copying the code to make sure it matches what was sent
Ensured my user email is confirmed (I heard that could be a problem)
Created a custom UserManager/Store etc.
This is my email code:
var user = await UserManager.FindByNameAsync(model.Email);
var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("ResetPassword", "Account", new { code }, "http");
var body = string.Format("Click here to reset your password: {0}", callbackUrl);
await UserManager.SendEmailAsync(user.Id, "Reset Password", body);
return View("~/Views/Account/Login.cshtml", model);
The generated URL:
http://localhost/Account/ResetPassword?code=XTMg3fBDDR77LRptnRpg7r7oDxz%2FcvGscq5Pm3HMe8RJgX0KVx6YbOeqflvVUINipVcXcDDq1phuj0GCmieCuawdgfQzhoG0FUH4BoLi1TxY2kMljGp1deN60krGYaJMV6rbkrDivKa43UEarBHawQ%3D%3D
Finally my reset code:
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
ModelState.AddModelError("","Invalid Password Please Try Again");
return View();
Inside the result is 1 error, Invalid token.
My create UserManager method:
public static CustomerUserManager Create(IdentityFactoryOptions<CustomerUserManager> options, IOwinContext context)
{
var manager = new CustomerUserManager(new CustomerUserStore(context.Get<CustomerDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<Customer>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
manager.EmailService = new EmailService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<Customer, string>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
My Startup.Auth config:
app.CreatePerOwinContext(CustomerDbContext.Create);
app.CreatePerOwinContext<CustomerUserManager>(CustomerUserManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity =
SecurityStampValidator.OnValidateIdentity<CustomerUserManager, Customer, string>
(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (id.GetUserId())
)
}
});
List of tried solutions:
ASP.NET Identity 2 - UserManager.ConfirmEmail Fails In Production
aspnet identity invalid token on confirmation email
http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-web-app-with-email-confirmation-and-password-reset#reset
Asp.NET - Identity 2 - Invalid Token Error
aspnet identity invalid token on confirmation email
https://aspnetidentity.codeplex.com/discussions/544368
Thanks for any help with this problem.
You can try this code.
I shared this link: aspnet identity invalid token on confirmation email
var encodedCode= code.Base64ForUrlEncode();
var decodedCode= encodedCode.Base64ForUrlDecode();
public static class UrlEncoding
{
public static string Base64ForUrlEncode(this string str)
{
byte[] encbuff = Encoding.UTF8.GetBytes(str);
return HttpServerUtility.UrlTokenEncode(encbuff);
}
public static string Base64ForUrlDecode(this string str)
{
byte[] decbuff = HttpServerUtility.UrlTokenDecode(str);
return Encoding.UTF8.GetString(decbuff);
}
}

Resources