I've got a custom Authentication middleware working for single sign-on. I'm wondering how I should go about implementing a single sign-out solution.
I need to call Authentication.Signout() to signout of my application, but I then need to redirect the user to the sign out endpoint of our custom STS. Where should I handle this? Invoke? ApplyResponseGrant? Not in the handler at all, but just a manual redirect?
edit:
This is an MVC app. I have everything working EXCEPT linking the local logout to logging out of the STS. Adding my existing code here would do nothing but obfuscate my question, IMO. If there is a specific piece of code that would help, let me know and I'll add it.
Ideally, I'd like some sort of event or flag that tells me the user is signing out, and then change the response into a 302 to the external logout. If I put this code in the ApplyResponseGrant, I have a feeling it will prevent the CookieAuthentication middleware from clearing the auth cookie. If I put this code in the Logout controller action (after a call to Authentication.SignOut()), then I leave it up to each application to handle the single sign off.
I got it working. Here's what I did.
In my AccountController, I added a Logout action that returns a Redirect("/signout-custom").
In my OWIN handler, I watch for that URL in the Invoke method, call my remote sign out endpoint, the local sign out method, and stop OWIN processing.
public override async Task<bool> InvokeAsync() {
//other code
if (Request.Path == Options.LogoutCallbackPath) {
Context.Authentication.SignOut(Options.AuthenticationType);
Response.Redirect(WebUtilities.AddQueryString(Options.ClauthLogoutUri, "returnUrl", "http://localhost:62506/Home/About"));
return true;
}
//other code
}
The redirect does not interrupt the OWIN flow, so the CookieAuthentication middleware still runs and clears the local auth cookie as it should.
Related
I am currently working on the logic of custom token validation. I need to deactivate the token when the user's password is changed (change-password endpoint is public).
I have implemented the ICustomTokenRequestValidator interface and resolved my class via DI
.AddCustomTokenRequestValidator<TokenHashValidatorService>();
However, I can see the following problem, my implementation of ICustomTokenRequestValidator only works when I generate a token and during only the first request to my API.
In logs I see the following information:
JWKS request from log
During first request to API request to /.well-known/openid-configuration and /.well-known/openid-configuration/jwks is sent. But when I send a second, third, etc. requests my breakpoint in TokenHashValidatorService is skipped.
Is there any way I can forcefully initiate second /.well-known/openid-configuration and /.well-known/openid-configuration/jwks requests?
Or maybe I can somehow mark that "token validation needed" during the change-password flow?
I'm really stuck and out of options, I've read all the articles out there, any ideas?
In my first exposure to Angular, I have an existing application using an Angular frontend and a .NET Web API for the backend. The pre-existing application was utilizing a login scheme with local accounts stored in the database. I am attempting to modify this app to use our organization's CAS server for authentication, instead.
The work I have done so far was based on the guidance found here: https://www.blinkingcaret.com/2018/10/10/sign-in-with-an-external-login-provider-in-an-angular-application-served-by-asp-net-core/
Thus far, I have made a few key changes. In authentication.service.ts, I have modified the login function to point to my new action on the server
login(){
this.document.location.href = this.casUrl + "login"
}
In my newly added CAS controller, I have the following actions.
public ActionResult Login() {
return new ChallengeResult("CAS");
}
public ActionResult HandleLogin() {
var claimsId = (ClaimsIdentity)User.Identity;
//Do things with claims, check against DB, etc
}
private class ChallengeResult : HttpUnauthorizedResult {
//sets the RedirectUri to HandleLogin, fires the Challenge in the ExecuteResult function
}
Up to this point, things seem to be behaving. The User.Identity populates correctly, so I can retrieve the corresponding user from the database. I am able to construct a LoginResponse object as well, though currently am not doing anything with it. This LoginResponse includes a token generated in another piece of the application, and seems to be a JWT.
At this point, I do not know how to transfer my LoginResponse back to the Angular.
In the original implementation using local accounts, a post was made to an API endpoint, and the response was piped to where it was needed.
return this.http.post(this.apiUrl + 'auth/login',
//parameters
), headers).pipe(
map((user:LoginResponse) => {
//do stuff with LoginResponse
}));
In turn, the component that called this service is subscribed to its return value.
My thought was to have a new function called within the init of a component. The first action it would take would be to go to my CasUrl endpoint and request the LoginResponse constructed using the User.Identity and DB lookup. Unfortunately, the Identity is empty upon all subsequent calls to the server. It does not seem to persist between requests.
One more note that may be relevant: My Web API solution is running through Visual Studio on localhost:46000, whereas the Angular application is running in VSCode on localhost:4200.
What is it I am missing to get external authentication working with this app? Is it possible to have an angular pipe or subscribe wait for results from an external website?
If I understand it correctly you do a full redirect to your CAS-server with location.href which does (after successful authentification) a redirect to your route /login.
So /login should response with the full angular site or redirect to the site with HttpStatus 302. The session cookie generated by the server can then be used for further requests to the backend.
The AccountService.ts can check if the user isAuthenticated by calling the api api/home/isAuthenticated This request should automatically append the newly gathered session cookie.
see example code of the blog you mentioned:
https://github.com/ruidfigueiredo/angular-aspnetcore-external-login/blob/master/AngularWithGoogleLogin/src/app/account.service.ts#:~:text=%20updateuserauthenticationstatus
and its counter piece:
https://github.com/ruidfigueiredo/angular-aspnetcore-external-login/blob/master/GoogleSpaWeb/Controllers/HomeController.cs#:~:text=public%20IActionResult-,IsAuthenticated,-()
Stef's answer is essentially what was required. I found that the session cookie was properly being created and stored in the browser, though I was used to the backend automatically maintaining session state.
I added a call to the API to verify login status, and as stated in the question, the User.Identity value was empty.
What I chose to do was create the LoginResponse object and assign its value to a cookie. Then, on the component's init function, I would check the cookie rather than calling to the API.
Currently upgrading a legacy MVC.NET v3 application to v5 while changing over from Forms Authentication to Microsoft's SSO solution. The requirement we would like to carry over is to force a logout if the user is idle for 30 minutes much like a banking application does due to the confidential information displayed within the application. We've implemented this successfully in the legacy application but I'm having trouble with the signout mechanism.
public void SignOut()
{
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType,
CookieAuthenticationDefaults.AuthenticationType);
}
Without changing a thing when a user clicks the signout button - it'll redirect them to Microsoft's account selector to choose which account to log out - even if there's only one choice. But if the user clicks the back button then they are back in the application which lets an authorized user see confidential information. I need to force a logout on the current account to prevent that but I'm not able to figure out how.
I tried:
Clearing cookies. Failed because if the bad actor clicks login again the current session remembers him and automatically logs them back in.
Overwriting the HttpContext.User - looks like it works. But again clicking on login will automatically refresh the old session because the Token Provider remembers the state.
How do I accomplish this?
Thanks,
I found this link to allow session variables with OWIN:
Can OWIN middleware use the http session?
You'll need to review this link to allow system web cookies working with OWIN:
ASP.NET_SessionId + OWIN Cookies do not send to browser
I created another method that signs the user out and creates a session variable.
public void ForceSignOut()
{
SignOut();
HttpContext.Session.Add("IsSignoutRequested", true);
}
If the user clicks the back button and comes back to the application, I force redirect them back into a challenge.
In my startup class I have set the PostLogoutRedirectUri which lets me create another method on a successful logout which abandons the session.
public ActionResult CompleteSignOut()
{
Session.Abandon();
Request.GetOwinContext().Authentication.SignOut();
Request.GetOwinContext().Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
this.HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "User",
action = "Index"
}));
}
I have been trying to wrap my head around this concept but have many questions and unfortunately, all official documents and tutorials are based on Visual Studio templates with individual user accounts.
My goal is pretty straightforward I believe. I have a web application which will only support external provider logins (namely: Facebook, Twitter, and LinkedIn). I do not want to support cookie authentication since there won't be a support for custom username/password.
My first problem is to define a default AuthenticationScheme. Below is my startup.cs:
services.AddAuthentication()
.AddFacebook(/* options */)
.AddTwitter(/* options */)
If I define a controller action with Authorize attribute I get no default authentication scheme defined error when I hit that route. However, I want users to be redirected to my login route if they are unauthorized. If I modify startup.cs like below it all works but then I think I support cookie (old forms authentication?) authentication which I don't want to.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddFacebook(/* options */)
My other issue is that I don't know what happens under the hood of AddFacebook() call. If I set up my middleware this way and log in with Facebook I magically get all the necessary tokens, claims and suddenly I have an application cookie set and my fb_login callback route can access to Facebook's token! When I check the network requests I see there is a hit to the signin-facebook route -which I didn't define- and I guess under the hood it calls HttpContext.SignInAsync() etc... but if I refresh my fb-login callback and check if
HttpContext.AuthenticateAsync(FacebookDefaults.AuthenticationScheme)
returns Success = true no! it returns false! But it was true just one second ago?
Also, when should I be using methods like AuthenticateAsync() and SignInAsync()?
Long story short I need a tutorial or documentation that explains this middleware without asp.net Identity framework, EntityFramework and templates.
I want to understand how a simple AddFacebook() call binds everything, and if I want to manually do that (say with AddOauth) how can I achieve the same functionality?
I'm not a fan of "automagically working" code so if someone can explain what's going on here I'd be very appreciated.
Cookie auth is used to persist the authenticated state between the requests. There is no substitute for this, and no it's not the same as forms auth, though cookies are used in both cases. The reason for that is simply that cookies are what makes state work over the HTTP protocol, which is itself stateless. If exclude the use of cookies, then there is no other mechanism to maintain state.
Using something like the Facebook auth scheme directly authorizes the initial request, but again, because there is no state, the next request is no longer authenticated without going through the Facebook OAuth flow again.
Long and short, the other auth schemes are there for things like APIs, where each request is typically authenticated individually via something like the Authorization header. A web browser doesn't work this way, and instead relies on cookies handle subsequent authorization. No cookies, no auth.
I'm using a custom AuthenticationHandler and only some of my controller methods have the [Authorize] attribute. I log on Info level and it creates a log entry for any method call. Now I can't tell apart if somebody actually tried accessing a method that requires authorization and failed or if it's a call to a method that doesn't even need authorization and it fails because it's supposed to.
Is there a way to tell them apart or preferably to keep MVC from calling HandleAuthenticateAsync when it's not needed?
It sounds like you might be misusing the AuthenticationHandler. You are not supposed to immediately reject access if the user fails to authenticate. If you do so, it doesn't even reach the MVC context in the pipeline.
Unless you intentionally want to reject any access to unauthenticated users, you should only either authenticate or pass it through as anonymous user. After that at some point AuthorizeAttribute will kick in and check whether user has access to the requested resource or not. If he is not authenticated, the authorization will reject the request.