My ASP.NET MVC 4 application is protected by SSO (OAM) with an ISAPI filter running on IIS. When a request to my application is received, it is intercepted by ISAPI filter and redirected to SSO. User has to login at SSO and after that he is returned to my application.
The username of authenticated user (via SSO) is shared with my application in HTTP Request Headers.
Request.Headers["username"]
What I am trying to achieve is- after SSO authentication, setting FormsAuthentication within my application for username = Request.Headers["username"]. This way SSO remains transparent to my application and Identity of user is available in HttpContext object, plus, I (developer) could effectively utlize Authorize attribute for specific roles.
To achieve this- I hookup into Session_Start(), read Request.Headers["username"], Set FormsAuthentication cookie. And I get this SSO user Forms-Authenticated for my application.
But my problem is when I logout (FormsAuthentication.Signout), I redirect it to another page inside the application, which triggers a new Session (I can see Session_Start triggering when this happens)
Am I doing the right thing- FormsAuthentication after SSO? And if not, why not and then how do I make my application aware of SSO authenticated user?
It's entirely reasonable to use FormsAuthentication cookies to track the logged in user in your application after they have been authenticated using a Single-Sign-On provider. You don't show it but I'm hoping that you are also getting some ticket that you can use to verify the signed in user out-of-band with the SSO provider and not simply trusting the username header.
What you may be seeing, however, is that the user is not signed out from the SSO provider when you sign them out of your application. Because of that, as long as they have a valid cookie for the SSO provider, they will remain signed in, i.e., the user will get automatically bounced back to your application from the SSO provider without any required authentication.
That's unfortunate, but normal.
If you truly want the user to be signed out, you'll need to make use of the centralized logout functionality. I haven't worked with OAM, but it appears that it does support this: http://docs.oracle.com/cd/E21764_01/doc.1111/e15478/logout.htm
I had to explicitly kill the session inside Session_Start if requested URL is logout URL. And then with next request (like from logout to login page again), it generates a new session and runs smoothly.
protected void Session_Start()
{
if (!Request.IsAuthenticated && !IsSignoutURL)
AcceptSessionRequest(); //process local authentication
else if (IsSignoutURL)
RejectSessionRequest(); //kill the sessions
}
For background on how SSO passes authenticated user's identity to my application, read my comment to tvanfosson's post.
The post remains opened for a better idea.
Related
The flow I am trying to figure out is this:
UserA is logs into ClientA
ClientA redirects to Idenitity Server to authenticate user
after authentication ClientA manages User info in its own system
UserA is found to be a "bad actor"
AdminUser goes into IdentityServer AdminTool (a different client application for managing the IdentityServer, including users).
AdminUser performs an action to "revoke" UserA
A call is made to the IdentityServer back-end from the admin tool where UserA has an "Enabled" property that is set to false.
At this point in the flow I want to kick the user out of ClientA from the back-end of the IdentityServer.
More context:
IdentityServer is using cookies in the client browser to keep the "session" (not sure if that is the right word cause there isn't actually any state being managed).
Also using cookies for remember-me.
Is there a way to remove the cookie for the IdentityServer from the back-end? Or notify the client that UserA should no longer have a valid authentication so that it can perform HttpContext.SignOutAsync()?
I was reviewing this link: https://docs.identityserver.io/en/latest/topics/signout.html for guidance but I am stuck on how to do this from the back-end as the AdminUser. Calling HttpContext.SignOutAsync() would sign out the AdminUser that made the request, not UserA that is causing havok in ClientA.
In a typical setup with identity server you have:
A JWT token with a short lifetime
A session cookie
The JWT token needs to be renewed frequently. The session cookie is used when there's no valid JWT. In this case the user will be redirected to the login page and if there's a session cookie the user will be automatically authenticated and redirected back to client app.
So you have some options for your logout:
Ensure the user cannot renew his JWT (= logout after a few minutes)
Ensure the user cannot login again
Really clear everything in the browser (and ensure he cannot login/renew again)
Back to your question
Is there a way to remove the cookie for the IdentityServer from the back-end?
The answer here is WebSockets which allows a two-way communication.
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
A common library for C# is Signal/R
https://dotnet.microsoft.com/apps/aspnet/signalr
You would create a Hob on the server side that allows you to send messages to the "bad user".
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-6.0
And listen for those messages in your client app:
https://learn.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-6.0
Please note: Whatever you do on the client side, the important part is typically to block the server requests. It's not secure to delete the cookie on the client. You have to delete the session on the server side instead.
Additional notes:
The flows in your setup may be different from what I described above.
I know, this is just a partial answer, but it's too much content for a comment ;-)
Should I Create only one client and all my applications must connect to the identity server using it or there is configuration should I do to make it work?
Identity Server is Web API, grant type is "resource owner password".
Each application is a client. But whether you should use the same client_id is up to you. Personally I use different client_id's for each instance of an application. This helps me to identify the instance and allows me to use different secrets per instance. And I think that the client_id has to be unique when you are using refresh tokens.
SSO is something you get for free when you use OpenId Connect (oidc). It works because of a cookie on the IdentityServer website:
When a user has to log in, the user is redirected to the IdentityServer website where (after succesful authentication) a cookie is set, containing information about the identity of the user.
When the user is returned to the client (after succesful authentication) the client will also set a cookie. As long as this authentication cookie is available, the user will be logged in.
When the user needs to log in again, because the client can't authenticate the user (anymore), IdentityServer will try to identify the user with the information from the cookie on the IdentityServer website and (if succesful) automatically sign in the user, skipping the login form.
But it seems that your setup has nothing to do with oidc. When you say that IdentityServer is a Web Api then this suggests that the user is not redirected to the IdentityServer to login, meaning that there is no opportunity to set a cookie.
And Resource Owner Password Credentials (ROPC) is an OAuth2 grant type. This suggests that the user performs the login on the client.
In order to enable SSO, the user has to login on the IdentityServer website. While ROPC is still possible, you'd better use one of the recommended grant types.
Topology
An internal OIDC server (OP) using the IdentityServer4 implementation.
An internal back-end client (RP) proxy web server for our SPA app.
An internal REST server that the RP uses as a resource.
Authentication flow
Hybrid, with back-channel-enabled OP and RP.
The problem
The RP is in a “sign-in illusion” state. When a user signs in via the RP, the OP and RP correctly create the server and local session cookie for the user with default expiration times.
There was no refresh-token flow in place, so there was a scenario where the user’s access token expired and they were still hitting the resource server with the expired token. This correctly returned 401 to the user.
What happens if I did set the user’s OP sign-in session to expire, but the RP decides to keep the user locally signed in indefinitely?
I thought of a few possible solutions:
Require every route to be challenged so that the redirect to sign-in flow kicks in.
Back-channel logout flow.
Option 2 seems the best considering our architecture, but I am wondering if it’s over-engineering. I really just need the RP to sign out its local user when said user has been signed out by the OP.
References
Idsrv signout
Front or back channel signout is the correct way to do it in my opinion.
Bare in mind though that it will only kick in if the user explicitly signs out.
You can also use session monitoring to be instantly notified if a user’s session changes and then take suitable action. Note however that the default implementation of this does not align the expiry of the session ID cookie with the main authentication cookie.
We have an ASP.NET/MVC website that's using FormsAuthentication. As is usual, when the user tries to access a page, and doesn't have a valid FormsAuthentication cookie, IIS redirects him to the login view. When the user does a HttpPost to the login controller, our controller action makes a call to our WebApi webservice, which validates username, password, and customerid against a Sql Server database. If the authentication passes, the controller action sets a FormsAuthentication cookie, and redirects to the page the user had asked for.
Now sales is making noises about "Single Sign-On", though I'm not clear exactly what they mean by that. From what I've read, in the Microsoft World this usually means accessing MS's Active Directory Federation Services.
At this point I have almost no idea how this would work, but before I dig into this too deeply, would it be possible to put the authentication code within the WebApi webservice, where we could choose to validate against the Sql Server database, or against whichever ADFS server was appropriate for the specified customer?
Our problem is that we have I don't know how many thousands of users, working for some hundreds of customers. Many customers will not have ADFS running, and those who do will each have their own ADFS server.
Most of what I see with respect to Single Sign-On seems to involve doing browser redirection to the ADFS server, then redirection back, and looks to be avoiding login at all, if you're already logged in. I don't think we can do that, in our case. We can't know which ADFS server to redirect to, until we hit the database.
So, the question - is it possible to do ADFS authentication entirely from C# code in our WebAPI web service?
(One possible complication - the website itself has zero access to any database. The sole configuration setting in its web.config is the base URL of the webservice. Whatever authentication happens has to happen in the webservice, not in the website.)
First of all, "Single Sign-On" (SSO) is not limited to ADFS. It simply means that you type your credentials only once, and then all systems you access automatically "recognize" you; all subsequent authorizations request are transparent. For instance, if you have several web sites using Windows Authentication in your company Intranet (same AD domain), you have SSO: you authenticate once when you log in to your computer, and then your web browser authenticates automatically to these web sites using NTLM or Kerberos. No ADFS in this case.
What ADFS (and "Federation" more generally) allows, is SSO accross security boundaries. In Windows world, a security zone is typically created by an Active Directory forest; everything within this forest is accessible using SSO provided by Windows authentication. But as soon as you leave this zone (SaaS application, web site in another company network), you need another authentication protocol to perform SSO, and these protocols are implemented in ADFS.
Then about your particular problem:
What you could do is instead of using FormsAuth, you use AdfsAuth. When a unknown user accesses a page, he would be redirected to ADFS for authentication (using browser redirects as you correctly mention). To know which ADFS server should authenticate your user, you need a way to differentiate them indeed: a list of IP range per customer? a different URL per customer? If you don't have something like this, then the only way is to show them a list of choices such as: "I work for CompanyA", "I work for CompanyB", "I work for CompanyC", "I don't work for any of these companies and want to authenticate using FormsAuth."
In this case, what your WebApi web service has to do is: if I know which ADFS server to use, redirect the user there. Otherwise authenticate the user as usual using the database.
When you use AdfsAuth for a customer, your database is useless. You can delete all credentials related to this customer.
do ADFS authentication entirely from C# code in our WebAPI
Well it's possible to "re-implement" ADFS in your service, but you won't get SSO if you do that. When you use federation, your redirect the user to the ADFS server of his company. This ADFS server is in the same domain as his computer, so the user gets SSO here. Once again, your users can't get SSO if you authenticate them yourself, because your users are not in the same security zone as your site.
When authenticating to multiple identity providers, it is typical redirect to your own STS. So, in this case, you would have www.yourapp.com redirecting to sts.yourapp.com, which redirects to sts.somecustomer.com.
The specific tools to enable such a dataflow is the home realm parameter (whr), and the AD FS Powershell API (to allow IDP maintenance).
Your RP-STS acts as the trust-point for the app, and manages selection of the appropriate IDP. One RP-STS, many IP-STS's. Each of your Customer's IP-STS gets set up as a Claims Provider Trust in AD FS.
As always, Vittorio has already covered the subject better than I could.
We are having a discussion about how forms authentication really works.
Is all information that identifies the user as being logged in stored in the cookie, or is some information stored in the session?
Information about the user being authenticated is stored in the FormsAuthenticationTicket in a cookie, by default named .ASPXAUTH.
Information about a user's session is separate from information about authentication. The identifier for session can be stored in a cookie (a different cookie from the authentication cookie) or, as Henk has pointed out, in a cookieless session i.e. as part of the URL.
The problem with storing some information about authentication in a user's session is that session is not available until some time after the authentication event (5 events later IIRC) in the processing pipeline, in PostAcquireRequestState. This means you wouldn't have access to the authentication data in session until after authentication!
It's possible to store data in session and to overwrite the IIdentity and IPrincipal with that data, but this does means that the user identity will have some data for the events before session is available and different data for the events after session is available, which may or may not be a problem. Furthermore, you'll probably want to cryptographically secure that data in session in some way.
To answer your title question, forms authentication does not require session; they are distinct entities required for different purposes.
For how Forms Authentication works, you can check out the below links:
Explained: Forms Authentication in ASP.NET 2.0
Understanding the Forms Authentication Ticket and Cookie
Forms Authentication works in web farm scenarios where the server handling a request from a Forms authenticated user may be different than the server that actually authenticated the user and issued the Forms authentication ticket and the cookie unless cookieless forms authentication is configured. To make this work, according to Web Farm Scenario section of the first link:
To address this issue, the validationKey and decryptionKey values must be identical on all computers in the Web farm. For more information about configuring the machineKey element, see How To: Configure MachineKey in ASP.NET 2.0.
which suggests that the Forms Authentication does not store anything in ASP.NET session. Otherwise, you would need to set up some form of an out-of-process session management in-place as well.
I also had a sample Forms Authentication application on hand and wanted to prove this quickly. After getting authenticated via Forms Authentication and landing on the home page, I restarted the application pool that the sample application was running in which should kill the user session. I then clicked on one of the links requiring authentication on the home page and was able to go to that link without getting redirected to the login page.