4

We have implemented our own MembershipProvider. I inherited the SQLMembership provider used the Microsoft ASP Membership SQL database. We have extended this database so that you could set a bit flag to enforce a user to change thier password. (This is ticked at user creation, and ticked again after policy expired period for example). It has also been extended to see if the person has already viewed and accepted our license agreement of using the site.

I'm now onto creating a Login page(s). The flow should be:-
1. User logs in.
2. Check db if user has confirmed license agreement.
True - Goto Step 3
False - Display page with license agreement and check box.
3. Check db if user needs to change password.
True - Display page to allow user to change password.
False - Goto Step 4
4. Redirect to page where user was originally heading.

All the demos I've seen online only have 1 login page. Once logged in you redirect to home page. How can I perform the above?
If the user decides not to confirm license how can I prevent them from typing in a URL and accessing the site, as they have already been authenticated in step one. We don't want the license agreement to be a link on the front login page, as we want to ensure the license agreement will appear to them if we reset the flag in the database, and force them to agree to it again before continuing.

I thought I found an example a few weeks back on the internet with multiple pages on login page, they used one page with hidden panels, but I've lost where I found it.

Any help would be appreciated. Thanks in advance.

2 Answers 2

3

In the end, I didn't use the ASP:Login control. I just designed my own login box with two textboxes. Username and password.

My login box, and my change password box are in two panels on the page. One hidden the other not. The person logs in, checks with the database if it is valid and if they need to change their password, if so, hides the login box panel, and displays the change password box. At this point, the user still isn't logged into SharePoint.

The user changes the password, the user is updated that they do not need to change their password on next login, and the panels are switched again. This is to allow the user to log in with their new password. This time as they log in they are properly logged in and authorized into SharePoint.

    <%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
<%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SignInPage.aspx.cs" Inherits="SharePoint.FBA.Layouts.Login.SignInPage" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
    <head>
        <link rel="icon" href="/_layouts/Images/Login/favicon.ico" type="image/x-icon" />
        <link rel="shortcut icon" href="/_layouts/Images/Login/favicon.ico" type="image/x-icon"/>
        <title>Sign In Page</title>
         <style type="text/css"> 
               html, body {height:100%; margin:0; padding:0;}    
               #page-background {position:fixed; top:0; left:0; width:100%; height:100%;}    
               #content {position:relative; z-index:1; padding:10px;}    
               .messages {font-family:Arial;font-size:12px;color:Red;font-style: italic;}
        </style>   
       </head>
    <body>
        <form id="form1" runat="server">
             <center>
                  <!-- Container for Sign-In Controls-->
                  <asp:PlaceHolder runat="server" ID="phPart1" Visible="true">
                    <div id="SignInContent" style="width: 300px; height: auto; margin-top: 15%; background:black;">
                         <table border="0" cellpadding="1" cellspacing="0" style="margin:20px;  width:100%">
                            <tr>
                                    <td align="left">
                                        <asp:Label ID="UserNameLabel" runat="server" ForeColor="White">User Name:</asp:Label>
                                    </td>
                                </tr>
                                <tr>
                                    <td>
                                        <asp:TextBox ID="UserName" Width="300px" runat="server"></asp:TextBox>
                                    </td>
                                    <td>
                                        <asp:RequiredFieldValidator ID="UserNameRequired" runat="server" ControlToValidate="UserName"
                                            ErrorMessage="User Name is required." ToolTip="User Name is required." ValidationGroup="SignInContent">*</asp:RequiredFieldValidator>
                                    </td>
                                </tr>
                                <tr>
                                    <td align="left" style="padding-top:10px">
                                        <asp:Label ID="PasswordLabel" runat="server" ForeColor="White" AssociatedControlID="Password">Password:</asp:Label>
                                    </td>
                                </tr>
                                <tr>
                                    <td>
                                        <asp:TextBox ID="Password" Width="300px" runat="server" TextMode="Password"></asp:TextBox>
                                    </td>
                                    <td>
                                        <asp:RequiredFieldValidator ID="PasswordRequired" runat="server" ControlToValidate="Password"
                                            ErrorMessage="Password is required." ToolTip="Password is required." ValidationGroup="SignInContent">*</asp:RequiredFieldValidator>
                                    </td>
                                </tr>
                                <tr>
                                    <td align="center" colspan="3" style="color: Red; font-size: small;">
                                        <asp:Literal ID="FailureTextLogin" runat="server" EnableViewState="False"></asp:Literal>
                                    </td>
                                </tr>
                                <tr>
                                    <td align="left" style="padding-top:10px">
                                        <asp:CheckBox ID="RememberMe" runat="server" ForeColor="White" Text="Remember me next time."/>
                                    </td>
                                </tr>
                                <tr>
                                    <td align="right">
                                        <asp:Button ID="LoginButton" runat="server" Text="Log in" OnClick="btnSubmit_OnClick" ValidationGroup="SignInContent"/>
                                    </td>
                                </tr>
                            </table>
                    </div>
                  </asp:PlaceHolder>
                  <!--Container for changing password-->
                  <asp:PlaceHolder runat="server" ID="phPart2" Visible="false">
                       <div id="ChangePassword" style="width: 300px; height: auto; margin-top: 15%; background:black;">
                             <table border="0" cellpadding="1" cellspacing="0" style="margin:20px;  width:100%">
                                 <tr>
                                    <td align="left">
                                        <asp:Label ID="CurrentPasswordLabel" runat="server" ForeColor="White">Password:</asp:Label>
                                    </td>
                                  </tr>
                                  <tr>
                                    <td>
                                        <asp:TextBox ID="CurrentPassword" Width="300px" runat="server" TextMode="Password"></asp:TextBox>
                                    </td>
                                    <td>
                                        <asp:RequiredFieldValidator ID="CurrentPasswordRequired" runat="server" ControlToValidate="CurrentPassword"
                                            ErrorMessage="Password is required." ToolTip="Password is required." ValidationGroup="ChangePassword">*</asp:RequiredFieldValidator>
                                    </td>
                                </tr>
                                <tr>
                                    <td align="left">
                                        <asp:Label ID="NewPasswordLabel" runat="server" ForeColor="White" Enabled="True">New Password:</asp:Label>
                                         <asp:CompareValidator ID="CompareValidator1" runat="server" ControlToCompare="NewPassword"
                                            ControlToValidate="ConfirmNewPassword" ErrorMessage="The confirm new password must match the new password."
                                            ToolTip="The confirm new password must match the new password." ValidationGroup="ChangePassword">*</asp:CompareValidator>
                                    </td>
                                </tr>
                                <tr>
                                    <td>
                                        <asp:TextBox ID="NewPassword" runat="server" Width="300px" TextMode="Password"></asp:TextBox>
                                       </td>
                                        <td>
                                        <asp:RequiredFieldValidator ID="NewPasswordRequired" runat="server" ControlToValidate="NewPassword"
                                            ErrorMessage="New password is required." ToolTip="New password is required."
                                            ValidationGroup="ChangePassword">*</asp:RequiredFieldValidator>
                                    </td>
                                </tr>
                                <tr>
                                    <td align="left">
                                        <asp:Label ID="ConfirmNewPasswordLabel" runat="server" ForeColor="White">Confirm New Password:</asp:Label>
                                         <asp:CompareValidator ID="NewPasswordCompare" runat="server" ControlToCompare="NewPassword"
                                            ControlToValidate="ConfirmNewPassword" ErrorMessage="The confirm new password must match the new password."
                                            ToolTip="The confirm new password must match the new password." ValidationGroup="ChangePassword">*</asp:CompareValidator>
                                    </td>
                                    </tr>
                                    <tr>
                                    <td>
                                        <asp:TextBox ID="ConfirmNewPassword"  Width="300px" runat="server" TextMode="Password"></asp:TextBox>
                                        </td>
                                        <td>
                                        <asp:RequiredFieldValidator ID="ConfirmNewPasswordRequired" runat="server" ControlToValidate="ConfirmNewPassword"
                                            ErrorMessage="Confirm new password is required." ToolTip="Confirm new password is required."
                                            ValidationGroup="ChangePassword">*</asp:RequiredFieldValidator>
                                    </td>
                                </tr>
                                <tr>
                                    <td align="center" style="color: Red; font-size: small; ">
                                        <asp:Literal ID="FailureTextPasswordChange" runat="server" EnableViewState="False"></asp:Literal>
                                    </td>
                                </tr>
                                <tr>
                                <td align="right" style="padding-top:10px">
                                        <asp:Button ID="ChangePasswordPushButton" runat="server" OnClick="ChangePasswordBtn_Click"
                                            Text="Change Password" ValidationGroup="ChangePassword" Padding-Right="5px" />
                                         <asp:Button ID="CancelPushButton" runat="server" CausesValidation="False" OnClick="Cancel_Click"
                                         Text="Cancel" />
                                    </td>
                                </tr>
                             </table>
                       </div>
                  </asp:PlaceHolder>
            </center>
       </form>
    </body>
</html>

The code behind looks like the following.

using System;
using System.Diagnostics;
using System.IdentityModel.Tokens;
using System.Text.RegularExpressions;
using SharePoint.FBA.Code.HelperClasses;
using Microsoft.SharePoint;
using Microsoft.SharePoint.IdentityModel;
using Microsoft.SharePoint.WebControls;

namespace SharePoint.FBA.Layouts.Login
{
    public partial class SignInPage : System.Web.UI.Page
    {
        private string _userName
        {
            get { return ViewState["vs_username"].ToString(); }
            set { ViewState["vs_username"] = value; }
        }

        private string _password
        {
            get { return ViewState["vs_password"].ToString(); }
            set { ViewState["vs_password"] = value; }
        }

        private bool _remember
        {
            get { return Convert.ToBoolean(ViewState["vs_remember"].ToString()); }
            set {ViewState["vs_remember"] = value;}
        }

        protected void btnSubmit_OnClick(object sender, EventArgs e)
        {
            _userName = UserName.Text.Trim();
            _password = Password.Text;
            _remember = RememberMe.Checked;

            if (string.IsNullOrEmpty(_userName))
            {
                FailureTextLogin.Text = "The server could not sign you in. The user name cannot be empty.";
                return;
            }

            if (string.IsNullOrEmpty(_password))
            {
                FailureTextLogin.Text = "The server could not sign you in. The password cannot be empty.";
                return;
            }

            var mem = (MyMembershipProvider) Utils.BaseMembershipProvider();
            if (!mem.ValidateUser(_userName, _password))
            {
                FailureTextLogin.Text = "The server could not sign you in. Make sure your username and password are correct, and then try again.";
                return;
            }


            var user = (MyMembershipUser) mem.GetUser(_userName, false);
            if (user != null)
            {
                if (user.IsPasswordChanged)
                {
                    phPart1.Visible = false;
                    phPart2.Visible = true;
                }
                else
                {
                    SignInUser();
                }
            }
        }

        protected void ChangePasswordBtn_Click(object sender, EventArgs e)
        {

            var mem = (MyMembershipProvider) Utils.BaseMembershipProvider();
            if (!mem.ValidateUser(_userName, CurrentPassword.Text))
            {
                FailureTextPasswordChange.Text = "Unable to change your password. Please make sure your current password is correct, and then try again.";
                return;
            }

            if (NewPassword.Text.Length < mem.MinRequiredPasswordLength)
            {
                FailureTextPasswordChange.Text =
                    String.Format("Your new password is too short. Minimum length {0} characters.",
                                  mem.MinRequiredPasswordLength);
                return;
            }

            int num =0;
            for (int i = 0; i < NewPassword.Text.Length; i++)
            {
                if (!char.IsLetterOrDigit(NewPassword.Text, i))
                {
                    num++;
                }
            }

            if (num < mem.MinRequiredNonAlphanumericCharacters)
            {
                FailureTextPasswordChange.Text =
                    String.Format("Your password requires {0} or more non-alphanumeric characters.", mem.MinRequiredNonAlphanumericCharacters);
                return;
            }

            Match match = Regex.Match(NewPassword.Text, mem.PasswordStrengthRegularExpression);
            if (!match.Success)
            {
                FailureTextPasswordChange.Text =
                    String.Format("Password must be at least {0} characters, containing upper, lowercase and numeric.",
                                  mem.MinRequiredPasswordLength);
                return;
            }

            var user = (MyMembershipUser) mem.GetUser(_userName, false);
            if (user != null)
            {
                try
                {
                    bool result = user.ChangePassword(CurrentPassword.Text, NewPassword.Text);
                    if (!result)
                    {
                        FailureTextPasswordChange.Text = "Unable to change your password.";
                        return;
                    }
                }
                catch (Exception ex)
                {
                    FailureTextPasswordChange.Text = ex.Message;
                    return;
                }

                    FailureTextLogin.Text =
                        "Successfully changed your password. Please login again with your new password.";
                    phPart1.Visible = true;
                    phPart2.Visible = false;
                }
            }

        protected void Cancel_Click(object sender, EventArgs e)
        {
            //Clear everything. Reset login page.
            _userName = "";
            _password = "";
            _remember = false;
            phPart1.Visible = true;
            phPart2.Visible = false;
            FailureTextLogin.Text =
                "Option to change password was cancelled. Unable to log you in unless password is changed.";
        }

        private void SignInUser()
        {
            var mem = (MyMembershipProvider) Utils.BaseMembershipProvider();
            var role = (MyRoleProvider) Utils.BaseRoleProvider();
            if (!mem.ValidateUser(_userName, _password))
            {
                return;
            }

            SecurityToken token = SPSecurityContext.SecurityTokenForFormsAuthentication
                                             (new Uri(SPContext.Current.Web.Url),
                                             mem.Name,
                                             role.Name,
                                             _userName,
                                             _password,
                                             _remember);
            SPFederationAuthenticationModule.Current.SetPrincipalAndWriteSessionToken(token);

            Response.Redirect(Request.QueryString["Source"]);
        }
    }
}

For completeness, my Utils class (found on the web is as follow)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;

namespace SharePoint.FBA.Code.HelperClasses
{
    public class Utils
    {
        public static MembershipProvider BaseMembershipProvider()
        {
            return Membership.Providers[GetMembershipProvider()];
        }

        public static RoleProvider BaseRoleProvider()
        {
            return Roles.Providers[GetRoleProvider()];
        }

        public static MembershipProvider BaseMembershipProvider(SPSite site)
        {
            return Membership.Providers[GetMembershipProvider(site)];
        }

        public static RoleProvider BaseRoleProvider(SPSite site)
        {
            return Roles.Providers[GetRoleProvider(site)];
        }

        public static string GetMembershipProvider()
        {
            return GetMembershipProvider(SPContext.Current.Site);
        }

        public static string GetRoleProvider()
        {
            return GetRoleProvider(SPContext.Current.Site);
        }

        public static string GetRoleProvider(SPSite site)
        {
            // get role provider of whichever zone in the web app is fba enabled 
            SPIisSettings settings = GetFBAIisSettings(site);
            return settings.FormsClaimsAuthenticationProvider.RoleProvider;
        }


        public static string GetMembershipProvider(HttpContext context)
        {
            using (SPSite site = new SPSite(SPUtility.GetPageUrlPath(context)))
            {
                return GetMembershipProvider(site);
            }
        }

        public static string GetMembershipProvider(SPSite site)
        {
            // get membership provider of whichever zone in the web app is fba enabled 
            SPIisSettings settings = GetFBAIisSettings(site);
            if (settings == null) return null;
            return settings.FormsClaimsAuthenticationProvider.MembershipProvider;
        }

        private static SPIisSettings GetFBAIisSettings(SPSite site)
        {
            SPIisSettings settings = null;

            // try and get FBA IIS settings from current site zone
            try
            {
                settings = site.WebApplication.IisSettings[site.Zone];
                if (settings.UseFormsClaimsAuthenticationProvider)
                    return settings;
            }
            catch
            {
                // expecting errors here so do nothing                 
            }

            // check each zone type for an FBA enabled IIS site
            foreach (SPUrlZone zone in Enum.GetValues(typeof(SPUrlZone)))
            {
                try
                {
                    settings = site.WebApplication.IisSettings[(SPUrlZone)zone];
                    if (settings.UseFormsClaimsAuthenticationProvider)
                        return settings;
                }
                catch
                {
                    // expecting errors here so do nothing                 
                }
            }

            // return null if FBA not enabled
            return null;
        }

    }
}

It is a long post, but hopefully it provide someone else with the full answer, which was something I was unable to find online.

2
  • Trying to do a variation of this but needs to attempt NTLM authentication first and passthrough if successful rather than displaying sign in screen. No joy so far, the examples I found are too old I'm using SharePoint 2010 custom login page at the moment but I'm on SP 2013 not 2010.
    – user692942
    Commented Apr 9, 2014 at 15:34
  • Thnx for the code. Maybe check the SPClaimsUtility.AuthenticateFormsUser instead of doing the token handling in your code. Commented Sep 16, 2015 at 10:24
1

I recently did something exactly like this. Your solution requires a bit of custom code though, but here's the flow:

  1. Setup your root site as anonymous allowed.

  2. Setup your secure content which you want only authenticated users to view to be a subsite of the root.. for example : http://bla.com/authenticated

  3. Wrap a Normal ASP.Net Login control into a web part. You can handle the change password flag in the Click event of the login control like this http://stackoverflow.com/questions/9770650/asp-net-membership-api-force-password-change

protected void LoginButton_Click(object sender, EventArgs e) 
{ 
    if (Membership.ValidateUser(UserName.Text, Password.Text)) 
    { 
        MembershipUser user = Membership.GetUser(UserName.Text); 
        if (user == null) 
        { 
           FailureText.Text = "Invalid username. Please try again."; 
           return; 
        } 
        if (user.IsLockedOut) 
           user.UnlockUser(); 

        /* this is the interesting part for you */ 
        if (user.LastPasswordChangedDate == user.CreationDate) //if true, that means user never changed their password before 
        { 
            //TODO: add your change password logic here 
        } 
    } 
}
  1. Setup the login web part on a page on the root site and make sure that the page has anonymous access allowed. Example http://bla.com/pages/login.aspx. Configure your sharepoint web application to point to that page as the default login page.

  2. Configure the login web part to redirect succesful to to the authenticated subsite :

http://bla.com/authenticated/pages/home.aspx

  1. For the agreement/disclaimer part, what I've done is create a web part and placed on http://bla.com/authenticated/pages/home.aspx. This web part onload checks the Profile Provider if the user has already clicked Agree and if Not, does the following :

    a Show a Modal with the agreement. b If the user clicks Agree, write back to the Profile Provider. c If the user disagrees, do a FormsAuthentication.Logout() and

    Response.Redirect("http://bla.com/pages/login.aspx")

I hope all this makes sense. Ping me if you need some of my code samples.

1
  • Fox, I like the idea of what you have implemented. I'm not to sure that the different sites (i.e authenticated) would work as the site is already built. It would require moving content. Maybe using multiple dialogs would allow me to perform the action I'm requiring. Commented Aug 3, 2012 at 14:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.