SharePoint Form Based Authentication
I have successfully set up form based authentication and m able to login non AD Users to sharepoint site (WSS 3.0). The login form(sharepoint page called login.aspx) asks for username and password.
Now i want to get rid of this login page and want to automatically post this page by passing the credentials may be in a querystring or otherwise.
I have tried changing the login.aspx page, able to trap the onLoad event of that page but not able to submit the page. Sharepoint is handling this event by passing the command to some of their DLL but actually dont know how it works. Do anybody having some idea about the sharepoint architecture or the back-end? How the things actually happening in the back end for Login mechanism. Any help on this would be really appreciated.
Hi,
i think you have to pass the Cookie. First, you create the cookie and then you do a RedirectFromLoginPage(). You have to enable the property "enablecrossappredirects"... for a forms single sign on.
If you are using FBA, I can most likely assume you are using a System.Web.Security.MembershipProvider object. SharePoint is only passing the form field values from the templated page to the MembershipProvider's ValidateUser method. Its a totally seamless abstraction that allows SharePoint to not have to manage that codebase or functionality. What you see in that page is all there is to it!! When you click the button, SP simply calls a method, which returns yes or no, then ASP.NET security kicks in through web.config processing, and either kicks you back to the login page, or grants access to the application when you POST. SharePoint is not doing any of that.
That abstraction and connection to the MembershipProvider means you can create a new login page, that does not implement the SharePoint Master Page, but shares the MembershipProvider, and of course is within scope for the application. This is where the magic happens. I would name it something other than login.aspx, as that file would most likely be overwritten by certain processes performed through SharePoint Central Administration. You may wish to disallow anonymous users from the default login page. The reason for this is because if you do some of the things in Central Admin (ie Deploying certain solutions) this file will most likely be overwritten or rewritten (if missing), so even if you delete it (thats how it would be missing!
), you cant assure users wont try to access it (because you just cant trust em!
). Disallowing will prevent this.
Modify web.config to use your new login page as the login page, within your SharePoint Application with the redirect pointing to your /default.aspx page, which should be by default. Additionally, make sure anonymous users can access that page.
On the new login page, add a Login Control and set the MembershipProvider to the one in your SharePoint app. On the Load event of the page, dynamically load the username and password fields programmatically (from your querystring or whatever), and then call the method delegated to the OnLoggingIn event of the control. The page should authenticate the user, create the cookie, and redirect to your SharePoint app, assuming the authentication is guaranteed to pass (ie, Lets say you have a shortcut on someone's desktop that points to http://yourspsite.com/somesubsite/default.aspx?u=245434526&p=FDGS556564 where "u" is your username param and "p" is your password param (both encrypted) and you know undeniably that the params will pass authentication). You dont need to waste any time with design efforts on the login page, as a user will never see it.
If there is a possiblity that the authentication may fail, then you probably wouldnt want to implement it this way, as you would still need some type of UI, which is clearly what youre trying to avoid in this scenario, otherwise you would just use the login page. Its possible you might want to use both however (login page for direct access to the URL or the dynamic method for integrating from within another application via shortcut), and this should be enough information for you to do so. If doing both, all you would have to do is create the dummy page you wish to use the querystring with and grant access to anonymous users, through web.config, then have your shortcuts point to dummypage.aspx?u=245434526&p=FDGS556564 while users logging in through the URL, would be redirected to login.aspx. As long as they are linked through the MembershipProvider and within scope, all should be smooth.
MAKE SURE that if you plan to authenticate a user, by passing their credentials through a querystring or through plain-text POST method, that the data is encrypted. POSTs are less of a risk, if using SSL, because the POST data is part of the Request, which would be encrypted by the browser prior to posting the request, whereas querystrings are not, but you should still use encryption.
** Note about modifying web.config outside of SharePoint:
In several cases, when implementing FBA and/or modifying the web.config file outside of SharePoint (Through IIS "ASP.NET Configuration Settings") the application ceased to work, and started throwing a "file not found" error. A generic error page showed up stating "file not found." The FBA login page is unable to access a SharePoint dll. A quick fix for this is to copy the Microsoft.SharePoint.ApplicationPages.dll to the GAC. Now, what will most likely happen is you will get the same error ("file not found"), but now it will at least be skinned by SharePoint, meaning the ApplicatonPages dll is now accessible. If youre still receiving the error, check the web.config file <configuration> element. Sometimes, when using the IIS utility, a namespace reference is added to the top of the page, which prevents the code from executing.
Find this:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
and replace it with this (delete the namespace reference):
<configuration>
You should no longer get the error, and your SharePoint should be back up with Forms-Based Authentication. You should also be able to login to the site "silently" after you implement your login page or dummy page.
If this post answered the question and/or was helpful, please mark it as so. Thank you. I urge everyone to revisit their threads, with any fixes or additional information they find to their problems so others can benefit.
I am doing exactly the same thing. Passing username and password parameters in the querystring and reading it in the sharepoint login page. It works fine till now , but I still wander how secure is this methid. I use SSL and encryption as well, but if someone manages to steal the data passed during the request he will be able to access the SharePoint site with no problems, having only the encrypted querystring. It is almost the same as having the real username and password. Isn't there a more secure way for passing user's credentials to the SharePoint site? In my case the application which is passing the credentials is a web application which is deployed even in the same IIS as my SharePoint site is!
Best regards,
pc
pc,
I agree there are some inherent security risks to doing it this way. One alternative would be to have the calling application (the one redirecting TO the SharePoint authentication dummy page) to generate a unique key that can only be used once, and is used for the encryption (GUIDs work nicely). You generate the key value, encrypt your credentials, pass them to the dummy page, validate the key hasnt been used yet, record the key in a database, decrypt the strings, and THEN authenticate the user's session if all else passes.
This way, the encryption key will be unique for every authentication attempt, and can therefore only be used one time, essentially as a composite key. By setting this as a key in your database (you only need to store the key, not all three values, as the username and password are calculated values, which are essentially static because any one key will always return the same encrypted values, so no need for overcomplexity), you can easily break out of the function if that key is violated from your code, or you can simply test for the existence of the record first if you choose.
Checking the value and then writing it if it passes is two steps. Creating the key in the DB, then simply trying to write it is one step than can be caught in the event of the intended exception. You return in the event of the exception and the execution is quite streamlined as you are never reading and writing using successive calls (just writing once to the DB), and you are only trying to decrypt and authenticate if the write passes, so resources are efficiently used.
Someone could steal the passed data all they want, and they will never gain access to the application, because as theyre stealing it, its already been posted, and therefore flagged as no longer valid. This is the only method I can come up with to truly secure the process, but would certainly eliminate the ability to use something like, say, a desktop shortcut which is static. .... or would it?
If you need to have something that simple for users, you could create a simple UI-less application with the user's credentials stored as (encrypted) text in a file, registry, etc.. Have a desktop shortcut pointing to that application, which does the above mentioned-process, and dynamically creates your URL with querystring when you launch the application.
From the shortcut application, you simply call System.Diagnostics.Process.Start(URLWithQueryString), then Application.Exit() to close the UI'less application. This will launch a browser, which requests the dummy page, authentication occurs, etc. and closes the shortcut application. As long as the shortcut app and the web app use the same encryption algorithm, all should be smooth. Voila! Problem solved.
If this post answered the question and/or was helpful, please mark it as so. Thank you. I urge everyone to revisit their threads, with any fixes or additional information they find relating to their problems so others can benefit.
Hi John,
Thank you for the very good idea. The only thing I still cannot understand in it yet is: in the decryption procedure how can I understand which exactly key to pick up from the database. If there are more than one key in the database used for encryption , with which one should I decrypt? Should I pass the key with the encrypted data as well, or put some other mark anywhere in order to know that this data has been encrypted exactly with this key?
I will not use a desctop shortcut or any static application, all the procedure consists of encrypting the username and password in the redirecting application, store the key in the database , and decrypt the data in the sharepoint login page.
Best regards,
pc
Hi John,
thanks a lot for the solution. Actually i struggled a lot with that, but couldn't implement it properly. So could you please help me a little more on that? i am giving you the code for login page which i have tried but could not even able to get the querystring parameters values to the textboxes. Can you please help me out on this:
<
script runat="server"> protected void Page_Load(object sender, EventArgs e) {
//UserName.Text = "Prashant"; //password.Attributes.Add("value", "prashant#"); }
protected void Login_Authenticate(object sender, AuthenticateEventArgs e) {
bool Authenticated = false; Authenticated =
FormsAuthentication.Authenticate(UserName.Text, password.Text); e.Authenticated = Authenticated;
}
//protected void login_LoggingIn(object sender, LoginCancelEventArgs e) //{ // FormsAuthentication.Authenticate(UserName.Text, password.Text); //} </
script> <
asp:Content ContentPlaceHolderId="PlaceHolderPageTitle" runat="server"> <SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,login_pagetitle%>" EncodeMethod='HtmlEncode'/> </
asp:Content> <
asp:Content ContentPlaceHolderId="PlaceHolderTitleBreadcrumb" runat="server">
</
asp:Content> <
asp:Content ContentPlaceHolderId="PlaceHolderPageTitleInTitleArea" runat="server"> <SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,login_pagetitle%>" EncodeMethod='HtmlEncode'/> </
asp:Content> <
asp:Content ContentPlaceHolderId="PlaceHolderSiteName" runat="server"/> <
asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server"> <asp:login id="login" FailureText="<%$Resources:wss,login_pageFailureText%>" runat=server width="100%" OnAuthenticate="Login_Authenticate" MembershipProvider="AspNetSqlMembershipProvider"> <layouttemplate> <asp:label id=FailureText class="ms-error" runat=server/> <table class="ms-input"> <COLGROUP> <COL width=25%> <COL WIDTH=75%> <tr> <td noWrap><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,login_pageUserName%>" EncodeMethod='HtmlEncode'/></td> <td><asp:textbox id=UserName autocomplete="off" Text="Prashant" runat=server class="ms-long"/></td> </tr> <tr> <td noWrap><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,login_pagePassword%>" EncodeMethod='HtmlEncode'/></td> <td><asp:textbox id=password Text="prashant#" autocomplete="off" runat=server class="ms-long"/></td> </tr> <tr> <td colSpan=2 align=right><asp:button id="login" commandname="Login" text="<%$Resources:wss,login_pagetitle%>" runat="server" /></td> </tr> <tr> <td colSpan=2><asp:CheckBox id=RememberMe text="<%$SPHtmlEncodedResources:wss,login_pageRememberMe%>" runat=server /></td> </tr> </table> </layouttemplate> </asp:login> </
asp:Content>
Please reply soon. Thanks in advance for your help.
In the page_load method you should do just this:
if (Membership.ValidateUser(username, password))
{
FormsAuthentication.RedirectFromLoginPage(username, false);
}
Where username and password are contained in the querystring in the url.
It is not needed to access the fields of the login page.
Regards,
pc
I would first like to recant my approach above in this thread. The Login control is not necessary, since in the long run you only want to get to the FormsAuthentication.redirectFromLoginPage() call anyway, so its redundant. The values can simply be trapped in the Load event from the querystring. See code sample below.
pc,
For clarification, the key would need to be passed with the querystring. Your calling app generates a GUID. Using that value, encrypt your params, and pass the key and both of your params to the authentication page. On the authentication page is where you validate the key hasnt been used. Using pseudocode, it might look something like this:
Calling Application (Passes login credentials for silent authentication)
public
void frmMain_Load(object sender, EventArgs e) {
// Ideally this value should be read from Text file, registry, etc. and optionally decrypted if need be string
username = "username"; string password = "password";
string encKey = Guid.NewGuid().ToString().Replace("-", "").ToUpper();
// Removes the dashes so no URL Encoding is necessary when passing as QueryString and convert to Upper Case.
// Make sure this is done before setting this as the encryption value, so no stupid mistakes are made with the wrong
// "right" value being passed.
string targetURL = "http://www.yourdomain.com/silentauthuser.aspx"; // or whatever the base page name may be
SomeEncryptionObject cipher =
new SomeEncryptionObject(); // Some encryption object that accepts an encryption key in some way, shape or form
cipher.key = encKey;
// Assign the key by whatever means available username = cipher.Encrypt(username);
// Assuming the encryption object has a similar mechanism. password = cipher.Encrypt(password);
/*
URLEncode the parameters to prepare them for the querystring as these values will most likely
contain characters that need to be encoded when passed in a URL (See sample URL below)
DO NOT call ToUpper() or ToLower() because the DEcryption will fail on the other end of the
pipe!!
*/
username =
HttpUtility.UrlEncode(username); password =
HttpUtility.UrlEncode(password); targetURL +=
string.Format("?ekey={0}&eu={1}&ep={2}", encKey, username, password); // Append the querystring to the base URL; (where eKey = encryption key, eu = encrypted username, ep = encrypted
// password)
// In a Winforms Application you could:
// Launch the URL in a new process System.Diagnostics.
Process.Start(targetURL); // Close the shortcut application; Application.Exit();
//In a web-based scenario, you could just do:
Response.Redirect(targetURL);
}
This will open http://www.yourdomain.com/silentauthuser.aspx?eKey=17C0C2F6F25C463A80B4119D0A9B99B2&eu=K8TvGdKnJypF6xXemn44Eg%61%61&ep=8d43W4GdAikzDTyk9YuEqA%61%61 <-This is actually a username and password encrypted using the indicated key with an encryption library I wrote, so I know this works, as I have tested it using the same dll on the server. (Its name is NOT SomeEncryptionObject
)
Receiving Application (Authentication Page which will redirect back into SP)
protected
void Page_Load(object sender, EventArgs e) {
string encKey = Request.QueryString["eKey"]; string username = Request.QueryString["eu"]; string password = Request.QueryString["ep"]; SomeDataHelperObject DB =
new SomeDataHelperObject(); try {
DB.Write(encKey);
// Try adding the value to the database. Assume the Write() method is fully implemented elsewhere and throws an // exception only if an internal exception occurs related specifically to the violation of the Unique Index value you are
// trying to add (the parsed GUID). I will not handle that code here.
// If the value already existed in the database table, the application would break here, authentication would not happen // and the catch handler would execute.
// URLDecode the passed params username =
HttpUtility.UrlDecode(username); password =
HttpUtility.UrlDecode(password); // Initialize your Encryptor (Decryptor) and set the key value to the passed key value
SomeEncryptionObject cipher =
new SomeEncryptionObject(); cipher.key = encKey;
// Decrypt the param strings
username = cipher.Decrypt(username);
password = cipher.Decrypt(password);
// We now have the username and password, safely passed across the web, and decrypted back to what it was read in
// as on the client end, so lets try validating it using the MembershipProvider, and if that passes, we redirect back into
// the application. This does not necessarily have to be a SharePoint site. This would work with any ASP.NET
// application configured to run in Forms-Based Authentication Mode.
if (Membership.ValidateUser(username, password))
{
FormsAuthentication.RedirectFromLoginPage(username, false);
}
throw new Exception(); // If it fails authentication throw an exception to force the catch handler to execute
}
catch
{
// SOME-thing should happen if it fails, or you WILL be getting calls from users! // Since the expected end result is a web experience, create dummy SharePoint-skinned page as a // landing point. This is good to do for 404 File Not Found errors as well. // If doing this, make sure unauthorized.aspx is accessible to anonymous users. (and your 404 Page!!) Response.Redirect(
"/unauthorized.aspx"); }
}
Thanks a lot John and pc
The solution you sent worked perfectly for me.
Thanks once again for your help.
Hi guy
I have recently implemented FBA and now I get from time to time the following error message
if a user tries to login:
Value cannot be null.
Parameter name: value at System.String.EndsWith(String value, StringComparison comparisonType)
at Microsoft.SharePoint.ApplicationRuntime.SPRequestModule.PostAuthenticateRequestHandler(Object oSender, EventArgs ea)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
There are no events in the event log, nor any error messages in the sharepoint log files.
Has anybody seen something similar? Hints would be appreciated very much.
Hans