Redirect users to login page after session timeout using OpenIdConnectAuthentication - asp.net

I'm using Azure B2C in an ASP.NET application with OpenIdConnectAuthentication. My sign-in policy has an absolute session length of 90 minutes.
After the session expires, I'd like the user to be automatically redirected to the login page, so they know they've been logged out. This doesn't seem to happen automatically. I was hoping this could be done automatically, perhaps using OWIN.
For now, as a workaround, I set a Refresh header in all HTTP responses that uses the remaining session life (calculated based on the iat claim and current time). But I was hoping this could just be handled automatically from the server. Is there any way to make the redirect occur automatically from the server-side once the session expires?
Below is my Startup class in Startup.Auth.cs:
public void ConfigureAuth(IAppBuilder app)
{
// ... other code ... //
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInPolicyId));
}
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy) {
var options = new OpenIdConnectAuthenticationOptions {
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = String.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
UseTokenLifetime = true,
// These are standard OpenID Connect parameters, with values pulled from
// Web.config
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = logoutUri,
Notifications =
new OpenIdConnectAuthenticationNotifications {
AuthenticationFailed = AuthenticationFailed,
RedirectToIdentityProvider =
(context) => {
var value = context.OwinContext.Request.Path.Value;
if (value != "/default.aspx") {
context.OwinContext.Response.Redirect("/");
context.HandleResponse();
}
return Task.FromResult(0);
}
},
Scope = "openid",
ResponseType = "id_token",
TokenValidationParameters =
new TokenValidationParameters {
NameClaimType = "name",
},
};
return options;
}

Related

Getting issue when I tried with integrating Microsoft Graph SDK with asp.net 4.8 web application

I am facing an issue with Microsoft GRAPH sdk.
IDX21323: RequireNonce is 'True'. OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The nonce cannot be validated. If you don't need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to 'false'. Note if a 'nonce' is found it will be evaluated.
public void ConfigureAuth(IAppBuilder app)
{ app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Authority = "https://login.microsoftonline.com/common/v2.0",
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
// For demo purposes only, see below
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}
}
);
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
private static Task OnAuthenticationFailedAsync(AuthenticationFailedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
string redirect = $"/ErrPage/Errormsg?message={notification.Exception.Message}";
if (notification.ProtocolMessage != null && !string.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription))
{
redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}";
}
notification.Response.Redirect(redirect);
return Task.FromResult(0);
}

User unauthorized after Azure AD login to different application simultaneously

I have two MVC applications AppA and AppB, and implemented Azure AD authentication for login.
I am able to sign-in successfully to both applications.
But the issue is, after I login to AppA and then to AppB, after sometime when I return back to AppA I am facing the issue where user has been logged out, and it again redirects to login screen (in AppA).
After I login to AppA (second time) and go back to AppB (user in AppB is logged out).
Client IDs are different ; TenandID is same. Both apps are hosted in same server.
Startup file:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider
{
OnResponseSignIn = context =>
{
context.Properties.AllowRefresh = true;
context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddDays(1);
},
OnValidateIdentity = MyCookieValidateIdentity
},
ExpireTimeSpan = TimeSpan.FromDays(2)
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
//CookieManager=new SameSiteCookieManager(new SystemWebCookieManager()),
Authority = "https://login.microsoftonline.com/xxxxxx/v2.0",
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.DomainHint = "xyz.com";
return Task.FromResult(0);
},
// SecurityTokenValidated = OnSecurityTokenValidated,
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}
}
);
}
actionContext.RequestContext.Principal.Identity.IsAuthenticated is returning False
I am assuming it has to do something with the cookie. Can someone please help resolve this ?
Edit:
Debugged further and found:
Initially if the cookies for AppA are set as:
.AspNet.Cookies = A_abc123 ; ASP.NET_SessionId = A_def456
And for AppB .AspNet.Cookies = B_mno123 ; ASP.NET_SessionId = B_pqr456
Then after I click any link in AppA, the cookie's values are updated with AppB's cookies, i.e. .AspNet.Cookies = B_mno123 ; ASP.NET_SessionId = B_pqr456
.AspNet.Cookies ASP.NET_SessionId
AppA A_abc123 A_def456
AppB B_mno123 B_pqr456
AppA B_mno123 B_pqr456
One thing that you need to do is to configure the Data Protection API so that both services uses the same cookie protection key. Out of the box each service creates its own unique key, and a cookie from one service is not valid in a different service.
I also did a blog post about the data protection API here.
See
How to: Use Data Protection
Get started with the Data Protection APIs in ASP.NET Core
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
//AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,// DefaultAuthenticationTypes.ApplicationCookie,
CookieName = ".AspNet.AppA.Cookies",
SlidingExpiration = true,
CookieManager = new SystemWebCookieManager(),
Provider = new CookieAuthenticationProvider
{
OnResponseSignIn = context =>
{
context.Properties.AllowRefresh = true;
context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddDays(1);
},
},
ExpireTimeSpan = TimeSpan.FromDays(2)
});
//... code removed for brevity //
}
The Default Cookie Name set by the application was: .AspNet.Cookies
And when I modified the default cookie name, the issue got resolved. Each application was generating its own cookiename and hence the other application was not signing out the user.

Get redirect link from client in IdentityServer3 login page

I would like to get redirectUrl from a client in Identity in IdentityServer3 in the login page.
for EX: I have a "localhost:54483/payments/5466cdaa-2005-4947-b4dc-cc6a49b83dfd/checkout" link
when I hit it , I will be redirected to a login page in IndentityServer and I need to get redirect link above (http://localhost:54483/payments/5466cdaa-2005-4947-b4dc-cc6a49b83dfd/checkout)
in
public class CustomViewService: DefaultViewService
{
private gtoken _gtoken;
public CustomViewService(DefaultViewServiceOptions config, IViewLoader viewLoader, gtoken gtoken) : base(config, viewLoader)
{
_gtoken = gtoken;
}
public override Task<Stream> Login(LoginViewModel model, SignInMessage message)
{
//TODO need to get redirect link here
return base.Login(model, message);
}
}
here is my client configuration:
public void Configuration(IAppBuilder app)
{
// turn off any default mapping on the JWT handler
AntiForgeryConfig.UniqueClaimTypeIdentifier = "sub";
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.Map("/api", idsrvApp =>
{
idsrvApp.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://localhost:5001",
ValidationMode = ValidationMode.Local, //set to validation endpoint if we want to support JWT revocation
RequiredScopes = new[] { "payment" }
});
});
Func<IOwinContext, bool> notApiRequest = (ctx) =>
{
return !ctx.Request.Path.StartsWithSegments(new PathString("/api"));
};
app.MapWhen(notApiRequest, idsrvApp =>
{
idsrvApp.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieName = Constants.AUTH_COOKIE_NAME
});
idsrvApp.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "http://localhost:5001",
ClientId = "06de763b-ad15-4225-a147-9f7b5da61cdf",
RedirectUri = "mylocal",
ResponseType = "id_token",
Scope = "openid",
SignInAsAuthenticationType = "Cookies",
});
});
}
I don't understand why would you want the redirect to happen there. I don't see the logic.
Have you read the documentation for identityServer3? You'll see there:
GET /connect/authorize?client_id=client1&scope=openid email api1&response_type=id_token token&redirect_uri=http://localhost:54483/payments/5466cdaa-2005-4947-b4dc-cc6a49b83dfd/checkout
*link: https://identityserver.github.io/Documentation/docsv2/endpoints/authorization.html
It means, when you see that the user is not logged in you send him to the login page of your identity server (even though the HTTP GET method above links to an endpoint, the identity server will show a login page), and in the request to the login page you would send an redirect url. Just make sure the redirect url is allowed for that client (check the documentation).
p.s. It is not recommended to keep the API and the identity server in the same project!

Asp.Net Identity with 2FA - remember browser cookie not retained after session

I'm using the latest sample code for MVC5.2 with Asp.Identity and Two Factor authentication.
With 2FA enabled, when a user logins, the get prompted for a code (sent by phone or email) and they have the option to "Remember Browser" - so that they don't get ask for codes again on that browser.
This is handled in the VerifyCode action
var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
Note that model.RememberMe is not used in the default templates so it is false.
I find when I do this the .AspNet.TwoFactorRememberBrowser that gets set, expires on session end (so it does not remember the browser)
Now if I set isPersistent = true, .AspNet.TwoFactorRememberBrowser gets an expiration of 30 days which is great, but the .AspNet.ApplicationCookie also gets a 30 day expiration - which means that when I close the browser and re-open, I am automatically logged in.
I want it so that it doesn't persist my login, but that it will persist my choice of remembering the 2FA code. Ie the user should always have to login, but they should not be asked for a 2fa code if they have already save it.
Has anybody else seen this, or am I missing something?
It doesn't seem like this code was designed to set more than one identity cookie in the same request/response because the OWIN cookie handlers end up sharing the same AuthenticationProperties. This is because the AuthenticationResponseGrant has a single principal, but the principal can have multiple identities.
You can workaround this bug by altering and then restoring the AuthenticationProperties in the ResponseSignIn and ResponseSignedIn events specific to the 2FA cookie provider:
//Don't use this.
//app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
//Set the 2FA cookie expiration and persistence directly
//ExpireTimeSpan and SlidingExpiration should match the Asp.Net Identity cookie setting
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
AuthenticationMode = AuthenticationMode.Passive,
CookieName = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
ExpireTimeSpan = TimeSpan.FromHours(2),
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider
{
OnResponseSignIn = ctx =>
{
ctx.OwinContext.Set("auth-prop-expires", ctx.Properties.ExpiresUtc);
ctx.OwinContext.Set("auth-prop-persist", ctx.Properties.IsPersistent);
var issued = ctx.Properties.IssuedUtc ?? DateTimeOffset.UtcNow;
ctx.Properties.ExpiresUtc = issued.AddDays(14);
ctx.Properties.IsPersistent = true;
},
OnResponseSignedIn = ctx =>
{
ctx.Properties.ExpiresUtc = ctx.OwinContext.Get<DateTimeOffset?>("auth-prop-expires");
ctx.Properties.IsPersistent = ctx.OwinContext.Get<bool>("auth-prop-persist");
}
}
});
Make sure to set the same ExpireTimeSpan and SldingExpiration as your main Asp.Net Identity cookie to preserve those settings (since they get merged in the AuthenticationResponseGrant).
This still appears to be an issue in Identity 2.2.1 (It may be fixed in Asp.Net Identity 3.0 - but that is currently pre-released and requires a later version of .Net framework that 4.5)
The following work around seems ok for now:
The cookie is getting set on the SignInManager.TwoFactorSignInAsync with the wrong values, so on Success of the VerifyCode action, I reset the cookie to be persistent and give it the expiry date that I wish (in this case I set it to a year)
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
} var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
switch (result)
{
case SignInStatus.Success:
// if we remember the browser, we need to adjsut the expiry date as persisted above
// Also set the expiry date for the .AspNet.ApplicationCookie
if (model.RememberBrowser)
{
var user = await UserManager.FindByIdAsync(await SignInManager.GetVerifiedUserIdAsync());
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(user.Id);
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTime.UtcNow.AddDays(365) }, rememberBrowserIdentity);
}
return RedirectToLocal(model.ReturnUrl);
What you can do is assign your own CookieManager class that modifies the expiration time of the TwoFactorRememberBrowserCookie. This seems better than modifing the cookie in Application_PostAuthenticateRequest.
This works around the problem that you can either persist all or none of the authentication cookies.
Put this in your ConfigureAuth, the last line sets your custom cookie manager.
public void ConfigureAuth(IAppBuilder app)
{
// left out all but the modified initialization of the TwoFactorRememberBrowserCookie
var CookiePrefix = ".AspNet.";
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
CookieName = CookiePrefix + DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
ExpireTimeSpan = TimeSpan.FromDays(14),
CookieManager = new TwoFactorRememberBrowserCookieManager()
});
}
Use this CookieManager class only for the TwoFactorRememberBrowserCookie.
When you do not persist cookies in TwoFactorSignInAsync, unfortunately the ExpirationTimeout is ignored.
So just set it again in the CookieManager (This is a modified version of the cookie manager coming from Microsoft.Owin.Infrastructure.CookieManager):
public class TwoFactorRememberBrowserCookieManager : Microsoft.Owin.Infrastructure.ICookieManager
{
string CookiePrefix = ".AspNet.";
Microsoft.Owin.Infrastructure.ICookieManager cm = new Microsoft.Owin.Infrastructure.ChunkingCookieManager();
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
if (key == CookiePrefix + DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie) {
options.Expires = DateTime.UtcNow.AddDays(14);
}
cm.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
cm.DeleteCookie(context, key, options);
}
public string GetRequestCookie(IOwinContext context, string key)
{
return cm.GetRequestCookie(context, key);
}
}
This is what you will get:
Works for me that way.

How do I ignore the Identity Framework magic and just use the OWIN auth middleware to get the claims I seek?

The OWIN middleware stuff to integrate third-party logins to your ASP.NET app is very cool, but I can't seem to figure out how to tear it out from the new ID framework that replaces the crappy Membership API. I'm not interested in persisting the resulting claims and user info in that EF-based data persistence, I just want the claims info so I can apply it to my own user accounts in existing projects. I don't want to adopt the new ID framework just to take advantage of this stuff.
I've been browsing the code on CodePlex, but there's a whole lot of static magic. Can you offer any suggestions?
Use the following code to setup OWIN security middlewares:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Application",
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString("/Login"),
LogoutPath = new PathString("/Logout"),
});
app.SetDefaultSignInAsAuthenticationType("External");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "External",
AuthenticationMode = AuthenticationMode.Passive,
CookieName = CookieAuthenticationDefaults.CookiePrefix + "External",
ExpireTimeSpan = TimeSpan.FromMinutes(5),
});
app.UseGoogleAuthentication();
The code above sets up application cookie, external cookie and Google external login middlewares. External login middleware will convert external user login data as identity and set it to external cookie middleware. In your app, you need to get external cookie identity and convert it to external login data, then you can check it with your db user.
Here are some sample code.
Sign in with application cookie:
var authentication = System.Web.HttpContext.Current.GetOwinContext().Authentication;
var identity = new ClaimsIdentity("Application");
identity.AddClaim(new Claim(ClaimTypes.Name, "<user name>"));
authentication.AuthenticationResponseGrant = new AuthenticationResponseGrant(identity, new AuthenticationProperties() {
IsPersistent = false
});
Get application cookie identity:
var identity = System.Web.HttpContext.Current.User.Identity as ClaimsIdentity;
Get external cookie identity (Google):
var authentication = System.Web.HttpContext.Current.GetOwinContext().Authentication;
var result = await authentication.AuthenticateAsync("External");
var externalIdentity = result.Identity;
Extract external login data from identity:
public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
{
if (identity == null)
{
return null;
}
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
|| String.IsNullOrEmpty(providerKeyClaim.Value))
{
return null;
}
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
{
return null;
}
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name)
};
}

Resources