IdentityServer4 and Angular: logout with Implicit flow - c#

I have an Angular app that integrates with IdentityServer4 with implicit flow and the angular-oauth2-oidc library.
Everything seems to work fine, I can log in; and access token is available.
If I click the logout button, I handle it like:
logout() {
this.oauthService.logOut();
}
...and I'm redirected to Identity Server, where it asks me if I really want to log out.
My question is whether I can bypass that prompt? I mean, I want to log out completely if the button is clicked and redirected back to the Angular site, without the need to confirm it?
How can this be achieved?
EDIT: as mentioned in the other answers, it should work if you pass id_token_hint. So I did:
logout() {
this.oauthService.customQueryParams = {
'id_token_hint': this.oauthService.getIdToken()
};
this.oauthService.logOut();
}
But it doesn't make any difference.

There were two issues I needed to fix in order to make this work.
In IdentityServer, AccountOptions class, I had to set this property to true instead of false:
public static bool AutomaticRedirectAfterSignOut = true;
Next, In IdentityServer client configuration, I had to define the post logout redirect uri:
PostLogoutRedirectUris = new List<string> {"http://...."}
That did it. I did not have to change anything in the Angular client.

Related

ASP.NET Core - Preserve POST data after Authorize attribute login redirect

This is essentially the same question as this one:
ASP.NET MVC - Preserve POST data after Authorize attribute login redirect except it isn't asked 7 years ago, and it's about ASP.NET Core, which is pretty different. I am using the [Authorize] attribute to do my most basic access authentication, really just to check to see if there is a user logged in at all. If not, then it kicks them back to the login page. Here's the services setup for that.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "loginId";
});
services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/Logout");
Here is my Logout action.
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Logout(string returnUrl = "/cgi-s/admin.cgi")
{
await HttpContext.SignOutAsync();
if (HttpContext.Request.Cookies.TryGetValue("loginId", out string loginId))
{
User auth = _context.Users.Where(a => a.LoginId.Equals(loginId)).FirstOrDefault();
if (auth != null)
{
auth.LoginId = string.Empty;
await _context.SaveChangesAsync();
}
}
return Redirect("/Account/Login?returnUrl="+returnUrl);
}
Right now I am just using the default behavior with a return url string to get back to the attempted page after a successful login. Is there a way to set this up so that POST data is also preserved?
I've tried a couple different things, like a custom middleware that stores post data which then gets retrieved on login, but I haven't come up with anything that I haven't found security holes in afterward.
Is there an easy way to do this that I'm just missing?
Thanks.
PS. Please ignore the weirdness going on in the Logout action. We are a two man team working on a 20 year old Perl CGI site, slowly transitioning over to .NET while trying to also keep up with new features and bug fixes, so everything is weird while we run Perl CGI alongside some .NET code on IIS with Postgres. Hopefully we will eventually get everything transitioned over.
Have you tried httpContext.Request.EnableBuffering?
See this question here and be sure to look at the comments: Read the body of a request as string inside middleware
httpContext.Request.EnableBuffering();
using(StreamReader streamReader = new StreamReader(httpContext.Request.Body,
...,leaveOpen: true))
{
//Do something here:
httpContext.Request.Body.Position = 0;
}

Cookie not deleted after logout with Asp.Net 5 Identity 3.0

I do have an Asp.Net MVC Application (version 6.0.0-rc1-final) with custom role and user stores. After some struggling I finally could create a working login mechanism. However I do have now troubles to create a clean logout. What my logout code in the controller currently looks like:
public async Task<ActionResult> Logout()
{
if (User.Identity.IsAuthenticated)
{
await SignInManager.SignOutAsync();
}
return RedirectToAction("Index", "App");
}
The problem with this code is, that one cookie is not deleted: .AspNet.Microsoft.AspNet.Identity.Application
As long as I don't delete the cookie manually the application is in a dirty state and throws null pointer exceptions because User.Identity is null.
I have found a question on stackoverflow describing a similar scenario. But the solution there is not appropriate for me because I am using MVC 6 which does not have System.Web any more.
I do also have a sample solution which just works fine. In this solution the mentioned cookie is never created. Perhaps the right solution is not to delete the cookie after logout, but rather to prevent somehow the creation of the cookie.
The problem is that your RedirectToAction overwrites the redirect to the Identity Server endsession URL that SignOutAsync issues.
(The same explanation for the same problem is given here by Microsoft's HaoK.)
Edit: The solution is to send a redirect URL in an AuthenticationProperties object with the final SignOutAsync:
// in some controller/handler, notice the "bare" Task return value
public async Task LogoutAction()
{
// SomeOtherPage is where we redirect to after signout
await MyCustomSignOut("/SomeOtherPage");
}
// probably in some utility service
public async Task MyCustomSignOut(string redirectUri)
{
// inject IHttpContextAccessor to get "context"
await context.SignOutAsync("Cookies");
var prop = new AuthenticationProperties()
{
RedirectUri = redirectUri
});
// after signout this will redirect to your provided target
await context.SignOutAsync("oidc", prop);
}
I could fix the dirty state of my application after the logout by manually delete the cookie after the logout action:
public async Task<ActionResult> Logout()
{
if (User.Identity.IsAuthenticated)
{
await SignInManager.SignOutAsync();
}
foreach (var key in HttpContext.Request.Cookies.Keys)
{
HttpContext.Response.Cookies.Append(key, "", new CookieOptions() { Expires = DateTime.Now.AddDays(-1) });
}
return RedirectToAction("Index", "App");
}
As cookies cannot deleted from the server directly I just overwrite the existing cookies with an already passed expiry date.
In addition to everything already mentioned, also make sure you are not omitting the scheme argument in the calls to SignInAsync and SignOutAsync, and that you are passing the same value to both. For example:
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
and
HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
So in this example the scheme is CookieAuthenticationDefaults.AuthenticationScheme. In my case I was forgetting to pass this to SignOutAsync, and while obvious after the fact, it took longer than I'd like to admit for me to track down.
Another gotcha that could leave the identity server cookies on the client is a logout failure. One typical cause of logout failures is a misconfiguration of the client’s PostLogoutRedirectUris.
The logout failures are not visible from the client side, the endsession call returns 200 OK, as well as the logout call.
There will however be traces on your identity server logs that the logout failed.

Prevent user from going back to the previous secured page after logout

I am facing an issue in MVC that i am able to visit the previous page on browser back button click even after getting logged out. I have few approaches:
1) Disable the Browser back button using window.history.forward().
This will give bad user experience.
2) Using outputCacheAttribute by providing the duration=0 but this will restrict both server side and client side caching. SO don't want to use this.
3) Adding below method in global.asax.cs
protected void Application_BeginRequest()
{
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetExpires(DateTime.UtcNow.AddHours(-1));
Response.Cache.SetNoStore();
}
Third approach will not allow to make the copy of the cache to the browser. Also to make this work I have to add the [Authorize] attribute on each controller.
This is not the best option for me as I have hundreds of controller. And tomorrow if I will add new controller then again I have to decorate the Authorize attribute to that new controller.
Is there any other approach that any one of you can suggest.
You can only add this attribute:
[OutputCache(NoStore = true, Duration = 0, Location = OutputCacheLocation.None)]
Public ActionResult SomeAction()
{
//
}
Disabling cache in specific action should be enough I guess.
Or if you still do not want to destroy cache just couple of places,
you can do that in your LOGIN function, you can add previous Attribute, or just use this when someone signs out:
Session.Clear();
Session.Abandon();
I wish it will help, since I havent got much time on testing it myself.

How to force logout user when his/her username is changed by another user?

In my application I am using Forms-Authentication to sign in and sign out users.
One functionality is admin can change the username of other users. In that case, I need to sign out the user whose username is changed.
If I do not, due to their cookies set before, they gain access to application and receive error messages (since their username does not exist and there are parts where I use their username for some functionality).
How can I force these users to log out using Forms-Authentication ?
UPDATE :
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string controller = filterContext.RouteData.Values["controller"].ToString();
string action = filterContext.RouteData.Values["action"].ToString(); ;
// Below returns the previous username, which does not exist anymore in db.
string userName = HttpContext.Current.User.Identity.Name;
UnitOfWork unitOfWork = new UnitOfWork();
if (!unitOfWork.UserRepository.UserExists(userName))
{
FormsAuthentication.SignOut();
filterContext.HttpContext.Session.Clear();
filterContext.HttpContext.Session.Abandon();
// I am not using Roles.
}
unitOfWork.Dispose();
base.OnActionExecuting(filterContext);
}
In my customer global filter, I check whether user exist or not, if not I sign them out. However, it is not working. By working I mean they pass the authentication and gain access to application.
Thanks in advance.
Here's what you do to force user to sign out:
public void UserPasswordChangedHandler()
{
FormsAuthentication.SignOut();
Roles.DeleteCookie();
Session.Clear();
}
I don't think line by line explanation required, its self explanatory enough.
Please let me know if I am mistaken.
Update
Straightforward answer to your additional question is to keep per user boolean tracking if his data was updated by admin and if yes - just redirect him to login page.
Please see following articles for forced logout using forms authentication information:
How can I force a user to log out
How can I force a log out of all users for a website,
ASP.NET forms authentication forced logout
How to log off multiple MembershipUsers that are not the current user
Update 2
Clearing cookies
HowTo: create and remove Cookies with ASP.NET MVC
How do you clear cookies in ASP NET MVC 3 and C#
How do I invalidate a bad authentication cookie
Hope this help you.
When a user needs to become invalidated you must add their details to some kind of internal static list.
Then on every page request (possibly using Application_BeginRequest) see if that current user is in that list, and if so to call FormsAuthentication.SignOut there-and-then.
It seems like a bit of a hack, but it's the best I can think of right now.
Note that removing a user-in-absentia's session state is another issue entirely.

How to use ServiceStack authentication correctly in ASP.Net MVC controller

I'm having problem with getting ServiceStack [Authentication] attribute to work in ASP.Net MVC4 controller, pages / action methods with the attribute keep redirecting Users to the login page even after the login details are submitted correctly.
I've followed the SocialBootstrapApi example, with the difference being that all the authentication web service calls are made from the controllers:
this.CreateRestClient().Post<RegistrationResponse>("/register", model);
Other things that I've done so far:
Use my own user session implementation subclassing AuthUserSession (not too different from the example, but using my own implementation of User table)
Inherit ServiceStackController on my BaseController, overriding the default login URL
Enable Auth feature in AppHost with my user session implementation
Registration does work, user auth logic works (even though the session does not persist), and I can see the ss-id and ss-pid cookies in the request.
So my complete list of questions:
How do I make the [Authenticate] attribute work (or, what did I do wrong)?
How do I save and reuse the user session in an MVC controller? At the moment this.UserSession is always null.
How do I logout a user? this.CreateRestClient().Get<AuthResponse>("/auth/logout"); does not seem to work.
Update 1:
The session cookies (ss-id and ss-pid) gets created when I attempt to load the secured page (ones with [Authenticate] attribute), before any credentials get submitted. Is this the expected behaviour?
Update 2:
I can see that the session is saved in MemoryCacheClient, however trying to retrieve it in the base controller via this.Cache.Get<CustomUserSession>(SessionKey) returns null (where SessionKey is like: urn:iauthsession:1)
After much fiddling around, apparently the way to hook ServiceStack authentication is to call the AuthService via:
try {
authResponse = AuthService.Authenticate(new Auth{ UserName = model.UserName, Continue = returnUrl, Password = model.Password });
} catch (Exception ex) {
// Cut for brevity...
}
and NOT authResponse = this.CreateRestClient().Post<AuthResponse>("/auth/credentials", model);!
Where AuthService is defined in the base controller as:
public AuthService AuthService
{
get
{
var authService = ServiceStack.WebHost.Endpoints.AppHostBase.Instance.Container.Resolve<AuthService>();
authService.RequestContext = new HttpRequestContext(
System.Web.HttpContext.Current.Request.ToRequest(),
System.Web.HttpContext.Current.Response.ToResponse(),
null);
return authService;
}
}
Everything else (incl. session) works correctly now.
You can find how it could be done in the ServiceStack Use Cases repository. The following example is based on MVC4 but works perfectly for MVC3 either: CustomAuthenticationMvc.

Categories

Resources