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.
Related
My project uses Azure AD login (https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-aspnet-core-webapp).
I might have configured it wrong, but this does not work well for Safari, likely because of SameSite cookie issues.
You can google "samesite safari" or "safari infinite loop login" and add asp.net core to it and find lots of resources telling you to fix this by changing your cookie to use SameSite None so Safari can log in.
But here the following is said:
The default MinimumSameSitePolicy value is SameSiteMode.Lax to permit OAuth2 authentication.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-5.0.
This doesn't seem right to me because I currently use secure, http only lax and that doesn't work well in Safari.
I am pulling my hair out over this. What is the right way to do external provider logins in 2021 with cookies?
There are a few different cookie scenarios here and standard settings are explained below. Cookies are a lot more complex than they appear at first sight.
AUTHORIZATION SERVER
Azure AD will use the following settings for the SSO cookie, used for single sign on across apps:
HTTP Only
Secure
SameSite=None
WEBSITE BASED APP - TEMP LOGIN COOKIE
.Net will issue a temp cookie before a login redirect containing a state parameter. This may be the one causing you problems - it should have these settings - strict will get dropped:
HTTP Only
Secure
SameSite=lax
The state value in the OAuth response to the browser is then verified against the value in the temp login cookie. Once this is verified, the app issues its own Auth cookie.
WEBSITE BASED APP - AUTH COOKIE
Your .Net Web app will issue its own Auth cookie once the OAuth response is processed. Ideally you want to use these settings:
HTTP Only
Secure
SameSite=strict
A few known causes for dropped cookies:
Using HTTP URLs on a developer PC means you need to set Secure=false in your app
Using pretend host names such as example.local configured in your computer's hosts file - Safari requires an internet suffix such as .com.
Cookies are encrypted and decryption failures are common causes of redirect loops in website based apps - this could be happening when .Net tries to process the temp login cookie.
If your Auth cookie uses SameSite=strict and you then navigate back to your app from an email link the Auth cookie will be dropped, leading to a new OAuth redirect. This results in some companies downgrading to SameSite=lax.
SINGLE PAGE APPS
SPAs can use a Back End for Front End pattern so that secure cookies are only ever needed during Ajax requests.
This means the app is never impacted by navigation issues, since cookies are never needed for web requests - they are only needed for API requests. In this model only data requests needs securing.
The SPA on my Quick Start Page uses this option and works fine in Safari.
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.
I have ASP.NET MVC 4 / Web API hybrid application. The authentication is being handled by an existing application. In looking at securing these types of applications, most articles point to using Forms Authentication along with the [Authorize] attribute on the MVC and API controllers/actions you want to protect. I would like to use the [Authorize] attribute as it will handle both MVC routes and API routes but not sure how to do that without having an actual form and using the built-in membership provider.
Should I go with a simple approach like described here? Or should I create a Custom Membership provider that handles the logic?
For clarity, the workflow would be as follows:
The user logs in through the existing authentication portal.
If authenticated, they are redirected to my application along with some additional data like username and email (so no passwords need to be transferred)
My application sets an authentication cookie that allows the user to continue using the application.
Any help would be greatly appreciated.
Option 1
If your existing login portal is using Forms Authentication, then you can share the encryption keys between the applications so that they can read the authentication cookie created in the login portal:
How can I share .net (C#) based authenticated session between web forms and MVC2 applications?
Option 2
If you're not using forms authentication in the login portal, you could keep your existing process, but add on a step that manually creates the authentication cookie. There are lots of variations on this, but this question show a higher level way in the question, and a lower level method in the answers:
How can I manually create a authentication cookie instead of the default method?
This also requires the encryption configuration be the same between the applications.
Option 3
Otherwise you are either left with using an SSO protocol for the handoff. You can keep the existing authentication process for your login portal, but will need to add additional code to coordinate the SSO handoff to the other application. SSO came about because other than encrypted cookie methods used in #1 & 2 above, there is little other secure options for doing a browser redirect and communicating authentication.
Creating your own cookie based method is risky and may open up security holes that you don't foresee.
"Should I go with a simple approach like described here?"
The important thing about that example is it's not clear where username is coming from in SetAuthCookie(username, .... That question implies that the user will login to the additional application and that app will query the web service to determine if that login is valid. In this case it's not single sign on with a dedicated login portal, but instead each app collects login information and asks the web API if it is valid. In your case, you do not want to collect the login information in each portal, but instead detect that they've already logged in to the deicated login portal.
So the problem is how does the login protal tell you in a secure fashion what username is when you call SetAuthCookie(username, .... That's exactly what SSO is for. Using a SSO handoff, one site can tell the other in a secure fashion that "I'm sending Bob123 to you, and you can be sure it is really Bob123 and not someone else.
Options #1 and #2 get around this by having the login portal set the cookie instead, and by sharing keys across the apps, the other apps can securely read that cookie.
Note you can't do this with just any cookie. The forms authentication cookie is built in a certain way to prevent forgery of the cookie and other tampering.
SSO becomes your only option if you are going across domains because cookies written in one domain cannot be read in another(the browser only submits cookies for the current comain).
There are workarounds for forms authentication with multiple subdomains sharing a root domain:
Proper creation of a cross-domain forms authentication cookie
There are various hacks for redirecting to another site with encrypted information and letting that site write the forms authentication cookie, but most of them are just horrible hacks that are just as complicated as SSO.
You seem to try to reinvent a Single Sign-On protocol. Instead, there are existing SSO protocols like OAuth2 or WS-Federation you should definitely learn about.
In general, SSO protocols work similarily to what you expect your "workflow" to behave. The exact flow can differ but this is always the Identity Provider that authenticates/authrorizes users and the IdP somehow passes this information to the application that makes use of it (for example, the application issues a custom cookie to establish user authentication).
The Authorize attribute is not intended to be used with Forms Authentication only. Any authentication module that sets the principal for the request lifetime can replace Forms. For example, the Session Authentication Module is often used nowadays as it fixes some particular issues of the Forms module (e.g. the inability to persist long user data).
If you need a good free book on SSO, take a look here:
http://msdn.microsoft.com/en-us/library/ff423674.aspx
You can use Owin to handle this. Here's a code snippet I am using to authentication using Facebook, it also uses cookie:
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
namespace ASPNetMVC53rdPartyAuth
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
// Use a cookie to temporarily store information about a user logging in with a
// third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// third party login providers:
// You have to register this app at https://developers.facebook.com/ and get the
//appId and appSecret.
// Facebook requires SSL, so that need to be enanbled. Project url can be found
// under project properties and can be localhost.
app.UseFacebookAuthentication(
appId: "xxxxxxxxxxxxxxxx",
appSecret: "xxxxxxxxxxxxxxxx");
);
}
}
}
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.
We're looking into doing machine-based authentication, allow any user on particular machines to access our .Net site. The list of machines will change but are all on an internal network. Their IP addresses are assumed to not be static.
To make it more manageable we'd like to be able to restrict access to an Active Directory computer group.
I can manage the AD querying, my question is where is the best place to pull out the machine name and authenticate the entire session?
I looked into creating a new HttpModule but it seems I'll have to authenticate every request. Authenticating every request doesn't seem ideal if an AD query is involved.
The web is stateless, so every request is always authenticated no matter what technology you're using. However, the trick to not hammering AD is using a session http cookie. You would set this cookie on the first request and check for it on subsequent requests. You would need some kind of cryptographic protection on the cookie, but thankfully this is ready-rolled in ASP.NET. I'd say you could leverage the Forms Authentication infrastructure to set and validate a cookie - you would just offload the initial authentication to AD instead of reading from a posted form.
Here's a great place to start:
Understanding the Forms Authentication Ticket and Cookie
http://support.microsoft.com/kb/910443
You don't need to authenticate every request. Authenticate the first request with your HttpModule, and either add the requestor to the session, or cache the credentials for a short period of time.