ASP.NET MVC Core 3.0 after login shows login page again at the browser back button click - asp.net-core-2.0

Once the user gets logged in and press the back button then the user needs to login again. I want to solve this issue. Can anyone have idea about it?
//startup.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options=>
{
options.Cookie.Name = "Dementia.Cookie";
//options.EventsType = typeof(Infrastructure.Persistance.GenericUser);
options.LoginPath = "/Home/Login";
options.LogoutPath = "/Home/Logout";
options.ExpireTimeSpan = TimeSpan.FromMinutes(5); //TimeSpan.FromDays(1);
options.SlidingExpiration = false;
});
//Controller
[Authorize]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Dashboard()
{
return View();
}

I have manually stored in cookies dashboard url and validate the identity its working fine.
var claims = new[] {
new Claim(ClaimTypes.Name, userInfo.Name),
new Claim(ClaimTypes.Role, userInfo.Category),
new Claim(ClaimTypes.Uri, "/Home/Dashboard"),
new Claim("UserId", userInfo.UserId),
new Claim("Id", userInfo.Id)};
public IActionResult Login()
{
if (HttpContext.User.Identity.IsAuthenticated)
{
return Redirect(HttpContext.User.FindFirst(ClaimTypes.Uri).Value);
}
return View();
}

Related

Angular Azure AD B2C Password reset policy after password changed logs into app and then shows login page

I am using Azure AD B2C with my Angular 11 application.
I am trying to use forgot password policy and it is working fine except after password is changed, it logs me into application and displays me my landing page and a fraction of second redirects to login page. Not sure what i am doing wrong.
Can anyone please help me how to resolve this?
this.msalBroadcastService.msalSubject$
.pipe(
filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_FAILURE || msg.eventType === EventType.ACQUIRE_TOKEN_FAILURE),
takeUntil(this._destroying$)
)
.subscribe((result: EventMessage) => {
// Checking for the forgot password error. Learn more about B2C error codes at
// https://learn.microsoft.com/azure/active-directory-b2c/error-codes
if (result.error && result.error.message.indexOf('AADB2C90118') > -1) {
let resetPasswordFlowRequest: RedirectRequest | PopupRequest = {
authority: b2cPolicies.authorities.resetPassword.authority,
scopes: [],
};
this.login(resetPasswordFlowRequest);
};
});
login(userFlowRequest?: RedirectRequest | PopupRequest) {
if (this.msalGuardConfig.interactionType === InteractionType.Popup) {
if (this.msalGuardConfig.authRequest) {
this.authService.loginPopup({ ...this.msalGuardConfig.authRequest, ...userFlowRequest } as PopupRequest)
.subscribe((response: AuthenticationResult) => {
this.authService.instance.setActiveAccount(response.account);
});
} else {
this.authService.loginPopup(userFlowRequest)
.subscribe((response: AuthenticationResult) => {
this.authService.instance.setActiveAccount(response.account);
});
}
} else {
if (this.msalGuardConfig.authRequest) {
this.authService.loginRedirect({ ...this.msalGuardConfig.authRequest, ...userFlowRequest } as RedirectRequest);
} else {
this.authService.loginRedirect(userFlowRequest);
}
}
}
I was trying to do the same thing the other day using user flows, not custom policy as per business requirement, However, I found out that after the password reset is finished, it redirects back to the app, but it can not sign in back using returned information through the URL. Not sure, if it is a bug or what, but it still tries to do that and timeout.
I would suggest after the password reset is successful, try to force logout e.g. remove MSAL stored information, and redirect to the login or landing page.
If I find a better solution, I will post it for you.
I have done a bit of research on this. I found out, once the password has been reset, the user is supposed to log in again. If you want to get the user logged in after the password has been reset, you need to authenticate the user in the API and return back the token. Here is the code for that:
public async Task<OAuthResult> AuthenticateCredentialsAsync(string username, string password)
{
var oauthEndpoint = new Uri(new Uri($"{_b2cSettings.Authority}{_b2cSettings.SignInROPC}/"), "oauth2/v2.0/token");
using (var client = new HttpClient())
{
var result = await client.PostAsync(oauthEndpoint, new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("client_id", _b2cSettings.ClientId),
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("client_info", "1"),
new KeyValuePair<string, string>("username", username),
new KeyValuePair<string, string>("password", password),
new KeyValuePair<string, string>("scope", $"openid {_b2cSettings.ClientId} offline_access "),
new KeyValuePair<string, string>("client_secret", _b2cSettings.ClientSecret),
new KeyValuePair<string, string>("response_type", "token id_token")
}));
if (result.StatusCode != HttpStatusCode.OK)
throw new AuthenticationFailedException(await result.Content.ReadAsStringAsync());
var content = await result.Content.ReadAsStringAsync();
var authResult = JsonConvert.DeserializeObject<OAuthResult>(content);
return authResult;
}
}
public class OAuthResult
{
public string Token_Type { get; set; }
public string Scope { get; set; }
public int Expires_In { get; set; }
public string Access_Token { get; set; }
public string Id_Token { get; set; }
public string Refresh_Token { get; set; }
public string Client_Info { get; set; }
}
NOTE: You need to create an ROPC policy in the Azure AD B2C.
Another option is to logout user after the password reset and redirection happens back to the App:
this.loggedInSuccess$ = new ReplaySubject<boolean>();
this.msalBroadcastService.msalSubject$
.pipe(
filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS || msg.eventType === EventType.ACQUIRE_TOKEN_SUCCESS)
)
.subscribe((result) => {
const payload: IdTokenClaims = <AuthenticationResult>result.payload;
if (payload.idTokenClaims?.tfp === b2cPolicies.names.forgotPassword) {
return this.msalService.logout();
} else if (payload.idTokenClaims?.tfp === b2cPolicies.names.signIn) {
this.loggedInSuccess$.next(true);
this.msalService.instance.setActiveAccount(payload.account);
}
return result;
});
If you have any issues, let me know, and I can post the full code.
Furthermore, you can handle more things as per your app requirements using the MSAL events:
https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-angular/docs/v2-docs/events.md

Blazor Server App with Azure AD authentication - Token expired and Custom AuthenticationStateProvider

I have built a Blazor Server App with Azure AD authentication. This server app access a web api written in net core and sends the JWT token to that api. Everything is working, data is gathered, page is displayed accordingly.
The problem is: after some time, when user interacts with some menu option in UI, nothing else is returned from webapi. After some tests I found out that the token has expired, then when it is sent to web api, it is not working. But the AuthenticationState remains same, like it is authenticated and valid irrespective the token is expired.
Thus, I have been trying some suggestions like : Client side Blazor authentication token expired on server side. Actually it is the closest solution I got.
But the problem is that, after implemented a CustomAuthenticationStateProvider class, even after injected it, the default AuthenticationStateProvider of the app remains like ServerAuthenticationStateProvider and not the CustomAuthenticationStateProvider I have implemented. This is part of my code:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly IConfiguration _configuration;
private readonly ITokenAcquisition _tokenAcquisition;
public CustomAuthenticationStateProvider(IConfiguration configuration, ITokenAcquisition tokenAcquisition)
{
_configuration = configuration;
_tokenAcquisition = tokenAcquisition;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var apiScope = _configuration["DownloadApiStream:Scope"];
var anonymousState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
string savedToken = string.Empty;
try
{
savedToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { apiScope });
}
catch (MsalUiRequiredException)
{
savedToken = string.Empty;
}
catch (Exception)
{
savedToken = string.Empty;
}
if (string.IsNullOrWhiteSpace(savedToken))
{
return anonymousState;
}
var claims = ParseClaimsFromJwt(savedToken).ToList();
var expiry = claims.Where(claim => claim.Type.Equals("exp")).FirstOrDefault();
if (expiry == null)
return anonymousState;
// The exp field is in Unix time
var datetime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiry.Value));
if (datetime.UtcDateTime <= DateTime.UtcNow)
return anonymousState;
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")));
}
public void NotifyExpiredToken()
{
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(authState);
}
private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var claims = new List<Claim>();
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);
if (roles != null)
{
if (roles.ToString().Trim().StartsWith("["))
{
var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());
foreach (var parsedRole in parsedRoles)
{
claims.Add(new Claim(ClaimTypes.Role, parsedRole));
}
}
else
{
claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
}
keyValuePairs.Remove(ClaimTypes.Role);
}
claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
return claims;
}
private byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
}
This is my Program.cs where I added the services :
builder.Services.AddScoped<CustomAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<CustomAuthenticationStateProvider>());
Here in the MainLayou.razor, I inject the service and try to use it :
#inject CustomAuthenticationStateProvider authenticationStateProvider;
protected async override Task OnInitializedAsync()
{
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User?.Identity == null || !authState.User.Identity.IsAuthenticated)
{
authenticationStateProvider.NotifyExpiredToken();
}
await base.OnInitializedAsync();
}
The problem comes up here, because the authenticationStateProvider is not an instance of the CustomAuthenticationStateProvider , but the instance of ServerAuthenticationStateProvider. It is like AuthenticationStateProvider was not replaced by the custom implementation, therefore I can't use the NotifyAuthenticationStateChanged and inform the CascadingAuthenticationState that it was changed.
If anyone has already been thru this or have any suggestion, it would be appreciated.
Actually I just wanna to change authentication state to not authenticated. So user will be pushed to login again using Azure AD.
Thanks

Azure AD Authentication redirects to home page

I'm building an Azure AD authentication which works fine, the only issue is that if a user clicks on a link that looks like host.com/xxx/bbb if they're not authenticated then they get redirected to root.
I need them to still be redirected to the original URL they entered in the browser. Can can this be achieved? Below is a snippet of the code I use in app startup:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = XXX,
Authority = string.Format("https://login.microsoftonline.com/{0}", YYY,
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = async n => { await Task.Run(() => SetRedirectUrl(n)); }
}
});
}
private static void SetRedirectUrl(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.ProtocolMessage.RedirectUri = notification.Request.Uri.AbsoluteUri;
}
None of the properties of OwinRequest contain a full path I'm looking for. I've also tried looking at
HttpContext.Current.Request.Url
but this also does not have the full address.
I need them to still be redirected to the original URL they entered in
the browser
You need to set the SignIn() method in AccountController.cs. You could use “Request.UrlReferrer.ToString();” to get the entered url:
public void SignIn()
{
// var host = Request.UserHostName.ToString();
var ori = Request.UrlReferrer.ToString();
var index = ori.LastIndexOf('/');
var action = ori.Substring(index);
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = action },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}

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.

Asp.net MVC-5 Identity suddenly not working

I'm developing web in Asp.Net Mvc5. But today suddenly I can't login /Using identity/.
Here's my login action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel input, string returnUrl)
{
if (ModelState.IsValid)
{
string personalNo = AppStaticConfig.ServiceController.PupilLogin(input.Code, input.Password, ModelState);
if (!string.IsNullOrEmpty(personalNo))
{
PupilProfile pp = AppStaticConfig.ServiceController.PupilDetail(personalNo, AppStaticConfig.SchoolID, ModelState);
var identity = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.Name, pp.pupil.LastName.Substring(0, 1) + "." + pp.pupil.FirstName),
new Claim(ClaimTypes.NameIdentifier, personalNo),
new Claim("pupilCode", input.Code)
},
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name, ClaimTypes.Role);
identity.AddClaim(new Claim(ClaimTypes.Role, "guest"));
Authentication.SignIn(new AuthenticationProperties{ IsPersistent = true }, identity);
TempData["pp"] = pp;
return RedirectToAction("PupilProfile", "Pupil");
}
}
return View(input);
}
And here's Pupil controller's PupilProfile action which redirected after successful login:
[Authorize]
public class PupilController : Controller
{
[HttpGet]
public ActionResult PupilProfile()
{
PupilProfile pp = TempData["pp"] != null ? TempData["pp"] as PupilProfile : AppStaticConfig.ServiceController.PupilDetail(User.Identity.GetUserId(), AppStaticConfig.SchoolID, ModelState);
return View(pp);
}
}
I think the problem is User.Identity not setting in Authentication.SignIn.
Any1 has idea what's wrong?
Problem solved. Was some environment problem I think.
What I was tried to fix:
Clear all cache cookie in browser. /But was no success/
Reset VS2013 settings from 'Import export menu'
Restart OS /my os:Win8.1/
Relaunched and problem was solved.
Hope it help some1.
i just switched to different browser and that was all.

Resources