User.Identity.IsAuthenticated AND _signInManager.IsSignedIn(User) return always null / fasle IN MVC CORE 6 2022 - asp.net-core

I have a new asp.net MVC core 6 application .try to authenticate users ( not by using Identity scaffolding ) .. however the the SignInmanger is always return False
Login function
programe.cs
Full code snippet for login :
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginVM loginvm)
{ // this wil return view model
if (!ModelState.IsValid)
{
return View(loginvm);
}
var user = await _userManager.FindByEmailAsync(loginvm.Username);
if (user != null)
{
// if we have user let us check the password
var checkpsssword = await _userManager.CheckPasswordAsync(user, loginvm.Password);
if (checkpsssword)
{
var letUserLoginIn = await _signInManager.PasswordSignInAsync(user, loginvm.Password, false, false);
if (letUserLoginIn.Succeeded)
{
var tempo = User.Identity.IsAuthenticated;
var isok = _signInManager.IsSignedIn(User);
ViewBag.tempo=tempo;
ViewBag.isok = isok;
return RedirectToAction("index", "Movie");
}
ModelState.AddModelError("Error","can login innnnn");
TempData["Error"] = "Password is not correct! !";
return View(loginvm);
}
else
{
// password wrong
TempData["Error"] = "Password is not correct! !";
}
}
TempData["Error"] = "no user found ya mozznoz!";
return View(loginvm);//STRONGLY TYPED VIEW
}

One part #Kevin have mentioned above, and another part was the missing of authentication mechanism register.
It should be something like builder.Services.AddAuthentication(opts => opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
AddAuthentication part add all the necessary middlewares and config to setup authentication process. Here, we specify DefaultScheme as CookieAuthenticationDefaults.AuthenticationScheme
AddCookie tell asp.net Core that we want to store the login information in cookie, therefore, a response that tell client to save a cookie with pre-defined information was sent(and the name for that authentication mechanism of choice was default to CookieAuthenticationDefaults.AuthenticationScheme).
For every subsequent requests, the cookie was included then server know, we already logged in

Related

Invalidate all authentication cookies in ASP.net CORE 3

On ASP.net CORE 3, when a user logout, I would like to invalidate all the cookies that exist on different devices. The user might have logged in from several different browsers, and the user has the option to use "Remember me" that lasts 30 days.
My understanding to solve this problem so far:
Use a securityStamp (a GUID) that I store in the database at the user level
Add this securityStamp in the Claims at login
When logout => change the securityStamp in the database
When http request arrives on a method of controller with [Authorize] attribute, check if the securityStamp match the one stored in the database. If not, redirect to login page.
My question is about point 4) where and how write this securityStamp check in the ASP.net CORE framework and redirect to login page ?
Here is my code at login time
string securityStamp = Guid.NewGuid().ToString();
saveSecurityStampInDB(securityStamp, user.Id);
var userClaims = new List<Claim>()
{
new Claim("id", user.Id.ToString()),
new Claim("securityStamp", securityStamp),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string")
};
var grantMyIdentity = new ClaimsIdentity(userClaims, "User Identity");
var userPrincipal = new ClaimsPrincipal(new[] { grantMyIdentity });
if (rememberMe.HasValue && rememberMe.Value)
{
await HttpContext.SignInAsync(userPrincipal, new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMonths(1)
});
}
else
{
await HttpContext.SignInAsync(userPrincipal);
}
UPDATE:
I have my own user table, I don't use entityFramework and the whole built-in Identity management.
You can use the SecurityStamp Property and the SecurityStampValidatorOptions.ValidationInterval Property to make the logout user's cookie invalid.
1.Register ValidationInterval in ConfigureServices
services.Configure<SecurityStampValidatorOptions>(options =>
{
options.ValidationInterval = TimeSpan.FromSeconds(1);//set your time
});
2.Add userManager.UpdateSecurityStampAsync()in your Logout like below
public async Task<IActionResult> Logout()
{
var userid = userManager.GetUserId(User);
var user = await userManager.FindByIdAsync(userid);
await userManager.UpdateSecurityStampAsync(user);
await signInManager.SignOutAsync();
return RedirectToAction("Index", "Home");
}
Result:

Multi-Factor Authentication is not working properly with ASP.NET Core 2.1 Identity

I've tried to implement a multi-factor authentication for my web application.
I'm able to generate an Authenticator-Key based on my logged-in user. Using Google Authenticator, I'm able to generate a code. The problem come up when I'm validating this code. (It's a time-based code)
For some reason, it seems like my _userManager.VerifyTwoFactorTokenAsync(...) doesn't work while the code is not expired, but as soon as the code change in the Google Authenticator app, the same "expired" code is now valid, and I'm now redirected and logged in.
This is my token generator code :
var user = await _userManager.GetUserAsync(User);
var authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
if (authenticatorKey == null)
{
await _userManager.ResetAuthenticatorKeyAsync(user);
authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
}
return View(new RegisterAuthenticatorViewModel { AuthenticatorKey = authenticatorKey });
And this is my code validation code :
var user = await _userManager.GetUserAsync(User);
var isValid = await _userManager.VerifyTwoFactorTokenAsync(user,
_userManager.Options.Tokens.AuthenticatorTokenProvider, model.Code);
if (!isValid)
{
ModelState.AddModelError(string.Empty, "Code is invalid");
return View(model);
}
await _userManager.SetTwoFactorEnabledAsync(user, true);
return View("_Success");
Since the code is actually working, my problem is to why it's validating the code only after it expired on the Google Authenticator App.

How to restrict Microsoft Account external login to a list of emails, or to one domain, in ASP.NET Core

I have a web app that is intended for a list of people that use Outlook as their email provider. The web app uses Microsofts external login api. But anyone with a Microsoft email account can log in at the moment.
I am assuming this is an app setting within Microsoft dev portal, or an override somehow within may code. How can I filter the app's domain or have a strict list of accounts that can login?
AFAIK , portal doesn't have setting to control that , so the workaround may be restrict the account type after redirecting back to your client from Microsoft's login page .
I assume you are using Microsoft.AspNetCore.Authentication.MicrosoftAccount:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/microsoft-logins?view=aspnetcore-2.2
You can check whether the email account is outlook account in ExternalLoginCallbackfunction of ASP.NET Identity :
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToAction(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
var emailAddress = info.Principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email).Value;
//Check whether this is outlook account .
if (!"outlook.com".Equals(emailAddress.Split('#')[1]))
{
//return to error page and show error message
}
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToAction(nameof(Lockout));
}
else
{
// If the user does not have an account, then ask the user to create an account.
ViewData["ReturnUrl"] = returnUrl;
ViewData["LoginProvider"] = info.LoginProvider;
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
}
}
Another way is checking in OnCreatingTicketevent in middleware ,if login user is not outlook account , redirect user to login page again :
services.AddAuthentication().AddMicrosoftAccount(microsoftOptions =>
{
microsoftOptions.Events = new Microsoft.AspNetCore.Authentication.OAuth.OAuthEvents
{
OnCreatingTicket = ctx =>
{
var email = ctx.Identity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
if (!"outlook.com".Equals(email.Value.Split('#')[1]))
{
ctx.Response.Redirect("/");
}
return Task.FromResult(0);
}
};
microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ApplicationId"];
microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:Password"];
});
Update :
If you are creating .net core application with Individual User Accounts template that means you are working with ASP.NET Identity :
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-2.2&tabs=visual-studio
ASP.NET Core 2.1 and later provides ASP.NET Core Identity as a Razor Class Library. You can scaffold Identity in ASP.NET Core projects to modify the code and change the behavior. Or you can try the second solution above to modify the OpenID Connect middleware .

Asp .Net Core, Identity Server 4, Check if user is authorized without authorized attribute

this is my code which works fine.If I'm authorized it works, if not, it redirects me to the identity server and after login back.
[Authorize]
public async Task<IActionResult> Index()
{
return View("../Secure/Index");
}
I want to return another view if user is not authorized and not to redirect him to the indentity server. This is how I tried it.
public async Task<IActionResult> Index()
{
if (User == null || User.Identity == null || !User.Identity.IsAuthenticated )
{
return View("../Public/Index");
}
return View("../Secure/Index");
}
However it is not working. User.Identity.IsAuthenticated is always false. Even when I was before on identity server and logged in. It seems that [Authorize] attribute is doing something more probably settings User.Identity.IsAuthenticated.
My question is, how do I know I'm authorized without attribute ?
Thank you
I spent three days figuring out how this can be done and come to conclusion THIS CAN'T BE DONE. Really great work Microsoft.
The closest I got is redirect to another
url: https://stackoverflow.com/a/43631950/4909067
But of course naming is different in .net core 2.1.
services.AddAuthentication(options =>
{
...
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
...
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = async (context) =>
{
Console.WriteLine( );
if (context.HttpContext.Request.Path.Value != "/Account/SignInWithOpenId")
{
context.HttpContext.Response.Redirect("/public");
context.HandleResponse();
}
await Task.CompletedTask;
},
};
...
})
But this is not what i want. I don't want to change url. I just want to serve some content when user is signed in and different when user is not signed in. On the same url. I should have chosen node js over this stupid .net core

Refresh token and roles are missing (OpenIddict)

my tokens are missing refresh and role property. I am using OpenIddict. The code did work until today and it still works on home computer, but not on work.
I am pretty sure I did something wrong, but since I compare startup.cs, AuthorizationController.cs and they are the same (work and home), I need some help what could be the source of problem.
I need to get roles for user which logins, because my Angular2 application needs to know what a user can do on web page.
Request I sent:
Work response:
Home response:
Startup code (again same on home computer):
services.AddOpenIddict<int>()
.AddEntityFrameworkCoreStores<AppDbContext>()
.AddMvcBinders()
.EnableTokenEndpoint("/API/authorization/token")
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.UseJsonWebTokens()
.AddEphemeralSigningKey() //todo naj bi bil pravi certifikat, če odstranič to vrstico ne dela in vidiš error.
.SetAccessTokenLifetime(TimeSpan.FromMinutes(30))
.SetRefreshTokenLifetime(TimeSpan.FromDays(14))
.DisableHttpsRequirement();
Controller code (again: same on home computer):
public class AuthorizationController : BaseController
{
public AuthorizationController(AppDbContext context, OpenIddictApplicationManager<OpenIddictApplication<int>> applicationManager, SignInManager<AppUser> signInManager, UserManager<AppUser> userManager) : base(context, applicationManager, signInManager, userManager)
{
}
[Authorize, HttpGet("authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
{
Debug.Assert(request.IsAuthorizationRequest(),
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
// Retrieve the application details from the database.
var application = await applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);
if (application == null)
{
return View("Error", new ErrorViewModel
{
Error = OpenIdConnectConstants.Errors.InvalidClient,
ErrorDescription = "Details concerning the calling client application cannot be found in the database"
});
}
// Flow the request_id to allow OpenIddict to restore
// the original authorization request from the cache.
return View(new AuthorizeViewModel
{
ApplicationName = application.DisplayName,
RequestId = request.RequestId,
Scope = request.Scope
});
}
[HttpPost("token"), Produces("application/json")]
public async Task<IActionResult> Exchange(OpenIdConnectRequest request)
{
Debug.Assert(request.IsTokenRequest(),
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
if (request.IsPasswordGrantType())
{
var user = await userManager.FindByNameAsync(request.Username);
if (user == null)
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The email/password couple is invalid."
});
}
// Ensure the user is allowed to sign in.
if (!await signInManager.CanSignInAsync(user))
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The specified user is not allowed to sign in."
});
}
// Reject the token request if two-factor authentication has been enabled by the user.
if (userManager.SupportsUserTwoFactor && await userManager.GetTwoFactorEnabledAsync(user))
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The specified user is not allowed to sign in."
});
}
// Ensure the user is not already locked out.
if (userManager.SupportsUserLockout && await userManager.IsLockedOutAsync(user))
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
// Ensure the password is valid.
if (!await userManager.CheckPasswordAsync(user, request.Password))
{
if (userManager.SupportsUserLockout)
{
await userManager.AccessFailedAsync(user);
}
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
if (userManager.SupportsUserLockout)
{
await userManager.ResetAccessFailedCountAsync(user);
}
// Create a new authentication ticket.
var ticket = await CreateTicketAsync(request, user);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
else if (request.IsRefreshTokenGrantType())
{
// Retrieve the claims principal stored in the refresh token.
var info = await HttpContext.Authentication.GetAuthenticateInfoAsync(
OpenIdConnectServerDefaults.AuthenticationScheme);
// Retrieve the user profile corresponding to the refresh token.
var user = await userManager.GetUserAsync(info.Principal);
if (user == null)
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The refresh token is no longer valid."
});
}
// Ensure the user is still allowed to sign in.
if (!await signInManager.CanSignInAsync(user))
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The user is no longer allowed to sign in."
});
}
// Create a new authentication ticket, but reuse the properties stored
// in the refresh token, including the scopes originally granted.
var ticket = await CreateTicketAsync(request, user, info.Properties);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported."
});
}
private async Task<AuthenticationTicket> CreateTicketAsync(
OpenIdConnectRequest request, AppUser user,
AuthenticationProperties properties = null)
{
// Create a new ClaimsPrincipal containing the claims that
// will be used to create an id_token, a token or a code.
var principal = await signInManager.CreateUserPrincipalAsync(user);
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
foreach (var claim in principal.Claims)
{
// In this sample, every claim is serialized in both the access and the identity tokens.
// In a real world application, you'd probably want to exclude confidential claims
// or apply a claims policy based on the scopes requested by the client application.
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(principal, properties,
OpenIdConnectServerDefaults.AuthenticationScheme);
if (!request.IsRefreshTokenGrantType())
{
// Set the list of scopes granted to the client application.
// Note: the offline_access scope must be granted
// to allow OpenIddict to return a refresh token.
ticket.SetScopes(new[] {
OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Roles
}.Intersect(request.GetScopes()));
}
ticket.SetResources("OpPISWeb"); //also in startup.cs
return ticket;
}
}
For decoding id_token I am using angular-jwt:
return this.http.post('api/authorization/token', this.encodeObjectToParams(data), options)
.map(res => res.json())
.map((tokens: AuthTokenModel) =>
{
console.log("loged in", tokens);
let now = new Date();
tokens.expiration_date = new Date(now.getTime() + tokens.expires_in * 1000).getTime().toString();
localStorage.setItem('id_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
const profile = this.jwtHelper.decodeToken(tokens.id_token) as ProfileModel;
const roles: string[] = typeof profile.role === "string" ? [profile.role] : profile.role;
const userProfile: Profile = new Profile(parseInt(profile.sub), roles);
localStorage.setItem('profile', JSON.stringify(userProfile));
this.refreshTokens(tokens.expires_in * 1000 * 0.8);
return profile;
});
The behavior you're seeing was caused by a bug introduced Friday. I fixed it a few minutes ago and new packages are being published at this moment.
Thanks for reporting it.