Combined Login Password Reset and Account Creation Control using AJAX Accordion

Creation of a combined login, password recovery, password reset and account creation control using AJAX Accordion, with a fix for the built Microsoft remember me

This article assumes that you have already downloaded the AJAX Toolkit, if not add it using the link below.
Creating a combined page to handle Logging In, creating a New Account, Password Recovery and Password Reset means that you can combine what would be four different pages into one, and in my opinion makes the site a lot more slick.
For this to work I would make sure you have a quick look at the following documents;

Add the User Store

Before we start, make sure you added your user store to the database.

Accordion Control

To start off, add the AJAX Toolkit Script Manager and an Accordian Control to your page, and add four panes as follows.

HTML

<asp:Accordion ID="Accordion1" runat="server" CssClass="accordion" ContentCssClass="accordionContent" HeaderCssClass="accordionHeader" HeaderSelectedCssClass="accordionHeaderSelected">
  <Panes>
  <asp:AccordionPane ID="Login" runat="server" CssClass="accordion">
  <Header>Login</Header>
  <Content>

  </Content>
  </asp:AccordionPane>
  <asp:AccordionPane ID="NewAccount" runat="server">
  <Header>New Account</Header>
  <Content>

  </Content>
  </asp:AccordionPane>
  <asp:AccordionPane ID="RecoverPassword" runat="server">
  <Header>Recover Password</Header>
  <Content>

  </Content>
  </asp:AccordionPane>
  <asp:AccordionPane ID="AccordionPane1" runat="server">
  <Header>Reset Password</Header>
  <Content>

  </Content>
  </asp:AccordionPane>
  </Panes>
</asp:Accordion>

Login Control (Page One)

That is the basis of the control, I am using slightly addapted versions of the built in controls, this means that the styles can be set across the board to match each other. First the Login Control, you can either drag one in from the Login Toolbox or use the code below. If you add your own then the Visual Basic may need to be adapted.

HTML

<asp:Login ID="Login1" runat="server" Width="100%" RememberMeSet="True">
  <LayoutTemplate>
  <table cellpadding="1" cellspacing="0" style="border-collapse:collapse; width: 100%;">
  <tr>
  <td>
  <table cellpadding="0" style="width:100%;">
  <tr>
  <td align="right" style="width: 30%;">
  <asp:Label ID="UserNameLabel" runat="server" AssociatedControlID="UserName">User Name:</asp:Label>
  </td>
  <td style="width: 70%;">
  <asp:TextBox ID="UserName" runat="server" style="width: 50%;"></asp:TextBox>
  <asp:RequiredFieldValidator ID="UserNameRequired" runat="server" 
  ControlToValidate="UserName" ErrorMessage="User Name is required." 
  ToolTip="User Name is required." ValidationGroup="Login1">*</asp:RequiredFieldValidator>
  </td>
  </tr>
  <tr>
  <td align="right" style="width: 30%;">
  <asp:Label ID="PasswordLabel" runat="server" AssociatedControlID="Password" style="width: 20%;">Password:</asp:Label>
  </td>
  <td style="width: 70%;">
  <asp:TextBox ID="Password" runat="server" TextMode="Password" style="width: 30%;"></asp:TextBox>
  <asp:RequiredFieldValidator ID="PasswordRequired" runat="server" 
  ControlToValidate="Password" ErrorMessage="Password is required." 
  ToolTip="Password is required." ValidationGroup="Login1">*</asp:RequiredFieldValidator>
  </td>
  </tr>
  <tr>
  <td colspan="2" align="center" >
  <asp:CheckBox ID="RememberMe" runat="server" Text="Remember me next time." />
  </td>
  </tr>
  <tr>
  <td align="center" colspan="2" style="color:Red;">
  <asp:Literal ID="FailureText" runat="server" EnableViewState="False"></asp:Literal>
  </td>
  </tr>
  <tr>
  <td align="center" colspan="2">
  <asp:Button ID="LoginButton" runat="server" Text="Log In" ValidationGroup="Login1" CommandName="Login" />
  </td>
  </tr>
  </table>
  </td>
  </tr>
  </table>
  </LayoutTemplate>
</asp:Login>

Create User Wizard (Pane Two)

Next we can add the Create User Wizzard, again I have used the built in control, but edited the template so that it is styled as I want. As before, you can use the code below or drag in your own.

HTML

<asp:CreateUserWizard ID="CreateUserWizard1" runat="server" Width="100%" ContinueDestinationPageUrl="/Home">
  <WizardSteps>
  <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
  <ContentTemplate>
  <table cellpadding="1" cellspacing="0" style="border-collapse:collapse; width: 100%;">
  <tr>
  <td align="right" style="width: 30%;">
  <asp:Label ID="UserNameLabel" runat="server" AssociatedControlID="UserName">User Name:</asp:Label>
  </td>
  <td style="width: 70%;">
  <asp:TextBox ID="UserName" runat="server" style="width: 50%;"></asp:TextBox>
  <asp:RequiredFieldValidator ID="UserNameRequired" runat="server" 
  ControlToValidate="UserName" ErrorMessage="User Name is required." 
  ToolTip="User Name is required." ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
  </td>
  </tr>
  <tr>
  <td align="right">
  <asp:Label ID="PasswordLabel" runat="server" AssociatedControlID="Password">Password:</asp:Label>
  </td>
  <td>
  <asp:TextBox ID="Password" runat="server" TextMode="Password" style="width: 30%;"></asp:TextBox>
  <asp:RequiredFieldValidator ID="PasswordRequired" runat="server" 
  ControlToValidate="Password" ErrorMessage="Password is required." 
  ToolTip="Password is required." ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
  </td>
  </tr>
   <tr>
  <td align="right">
  <asp:Label ID="ConfirmPasswordLabel" runat="server" 
  AssociatedControlID="ConfirmPassword">Confirm Password:</asp:Label>
  </td>
  <td>
  <asp:TextBox ID="ConfirmPassword" runat="server" TextMode="Password" style="width: 30%;"></asp:TextBox>
  <asp:RequiredFieldValidator ID="ConfirmPasswordRequired" runat="server" 
  ControlToValidate="ConfirmPassword" 
  ErrorMessage="Confirm Password is required." 
  ToolTip="Confirm Password is required." ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
  </td>
  </tr>
  <tr>
  <td align="right">
  <asp:Label ID="EmailLabel" runat="server" AssociatedControlID="Email">E-mail:</asp:Label>
  </td>
  <td>
  <asp:TextBox ID="Email" runat="server" style="width: 50%;"></asp:TextBox>
  <asp:RequiredFieldValidator ID="EmailRequired" runat="server" 
  ControlToValidate="Email" ErrorMessage="E-mail is required." 
  ToolTip="E-mail is required." ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
  </td>
  </tr>
  <tr>
  <td align="right">
  <asp:Label ID="QuestionLabel" runat="server" AssociatedControlID="Question">Security Question:</asp:Label>
  </td>
  <td>
  <asp:TextBox ID="Question" runat="server" style="width: 80%;"></asp:TextBox>
  <asp:RequiredFieldValidator ID="QuestionRequired" runat="server" 
  ControlToValidate="Question" ErrorMessage="Security question is required." 
  ToolTip="Security question is required." ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
  </td>
  </tr>
  <tr>
  <td align="right">
  <asp:Label ID="AnswerLabel" runat="server" AssociatedControlID="Answer">Security Answer:</asp:Label>
  </td>
  <td>
  <asp:TextBox ID="Answer" runat="server" style="width: 30%;"></asp:TextBox>
  <asp:RequiredFieldValidator ID="AnswerRequired" runat="server" 
  ControlToValidate="Answer" ErrorMessage="Security answer is required." 
  ToolTip="Security answer is required." ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
  </td>
  </tr>
  <tr>
  <td align="center" colspan="2">
  <asp:CompareValidator ID="PasswordCompare" runat="server" 
  ControlToCompare="Password" ControlToValidate="ConfirmPassword" 
  Display="Dynamic" 
  ErrorMessage="The Password and Confirmation Password must match." 
  ValidationGroup="CreateUserWizard1"></asp:CompareValidator>
  </td>
  </tr>
  <tr>
  <td align="center" colspan="2" style="color:Red;">
  <asp:Literal ID="ErrorMessage" runat="server" EnableViewState="False"></asp:Literal>
  </td>
  </tr>
  </table>
  </ContentTemplate>
  <CustomNavigationTemplate>
  <table border="0" cellspacing="5" style="width:100%;height:100%;">
  <tr align="right">
  <td align="right" colspan="0" style="text-align: center">
  <asp:Button ID="StepNextButton" runat="server" CommandName="MoveNext" Text="Create User" ValidationGroup="CreateUserWizard1" />
  </td>
  </tr>
  </table>
  </CustomNavigationTemplate>
  </asp:CreateUserWizardStep>
  <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
  <ContentTemplate>
  <table style="font-size:100%;width:100%;">
  <tr>
  <td style="text-align: center">
  Your account has been successfully created. Click continue to go through to personalise your experience.</td>
  </tr>
  <tr>
  <td align="center">
   <asp:Button ID="ContinueButton" runat="server" CausesValidation="False" CommandName="Continue" Text="Continue" ValidationGroup="CreateUserWizard1" />
  </td>
  </tr>
  </table>
   </ContentTemplate>
  </asp:CompleteWizardStep>
  </WizardSteps>
  <FinishNavigationTemplate>
  <asp:Button ID="FinishPreviousButton" runat="server" CausesValidation="False" 
  CommandName="MovePrevious" Text="Previous" />
  <asp:Button ID="FinishButton" runat="server" CommandName="MoveComplete" 
  Text="Finish" />
  </FinishNavigationTemplate>
  <StartNavigationTemplate>
  <asp:Button ID="StartNextButton" runat="server" CommandName="MoveNext" 
  Text="Next" />
  </StartNavigationTemplate>
  <StepNavigationTemplate>
  <asp:Button ID="StepPreviousButton" runat="server" CausesValidation="False" 
  CommandName="MovePrevious" Text="Previous" />
  <asp:Button ID="StepNextButton" runat="server" CommandName="MoveNext" 
  Text="Next" />
  </StepNavigationTemplate>
</asp:CreateUserWizard>

Recover Password (Pane Three)

Again a standard control that I have edited the template to. This control is the only one we don't use extra code behind for.

HTML

<asp:PasswordRecovery ID="PasswordRecovery1" runat="server" Width="100%">
  <QuestionTemplate>
  <table cellpadding="0" style="width:100%;">
  <tr>
  <td align="center" colspan="2">
  Identity Confirmation</td>
  </tr>
  <tr>
  <td align="center" colspan="2">
  Answer the following question to receive your password.</td>
  </tr>
  <tr>
  <td align="right" style="width: 30%">
  User Name:</td>
  <td>
  <asp:Literal ID="UserName" runat="server"></asp:Literal>
  </td>
  </tr>
  <tr>
  <td align="right" style="width: 30%">
  Question:</td>
  <td>
  <asp:Literal ID="Question" runat="server"></asp:Literal>
  </td>
  </tr>
  <tr>
  <td align="right" style="width: 30%">
  <asp:Label ID="AnswerLabel" runat="server" AssociatedControlID="Answer">Answer:</asp:Label>
  </td>
  <td>
  <asp:TextBox ID="Answer" runat="server" Width="50%"></asp:TextBox>
  <asp:RequiredFieldValidator ID="AnswerRequired" runat="server" 
  ControlToValidate="Answer" ErrorMessage="Answer is required." 
  ToolTip="Answer is required." ValidationGroup="PasswordRecovery1">*</asp:RequiredFieldValidator>
  </td>
  </tr>
  <tr>
  <td align="center" colspan="2" style="color:Red;">
  <asp:Literal ID="FailureText" runat="server" EnableViewState="False"></asp:Literal>
  </td>
  </tr>
  <tr>
  <td align="center" colspan="2">
  <asp:Button ID="SubmitButton" runat="server" CommandName="Submit" Text="Submit" ValidationGroup="PasswordRecovery1" />
  </td>
  </tr>
  </table>
  </QuestionTemplate>
  <SuccessTemplate>
  <table cellpadding="0" style="width:100%;">
  <tr>
  <td align="center">
  Your password has been sent to you.</td>
  </tr>
  </table>
  </SuccessTemplate>
  <UserNameTemplate>
  <table cellpadding="0" width="100%">
  <tr>
  <td align="center" colspan="2">
  Forgot Your Password?</td>
  </tr>
  <tr>
  <td align="center" colspan="2">
  Enter your User Name to receive your password.</td>
  </tr>
  <tr>
  <td align="right" style="width: 30%;">
  <asp:Label ID="UserNameLabel" runat="server" AssociatedControlID="UserName">User Name:</asp:Label>
  </td>
  <td align="left" style="width: 70%;">
  <asp:TextBox ID="UserName" runat="server" style="width: 50%;"></asp:TextBox>
  <asp:RequiredFieldValidator ID="UserNameRequired" runat="server" 
  ControlToValidate="UserName" ErrorMessage="User Name is required." 
  ToolTip="User Name is required." ValidationGroup="PasswordRecovery1">*</asp:RequiredFieldValidator>
  </td>
  </tr>
  <tr>
  <td align="center" colspan="2" style="color:Red;">
  <asp:Literal ID="FailureText" runat="server" EnableViewState="False"></asp:Literal>
  </td>
  </tr>
  <tr>
  <td align="center" colspan="2">
  <asp:Button ID="SubmitButton" runat="server" CommandName="Submit" Text="Submit" 
  ValidationGroup="PasswordRecovery1" />
  </td>
  </tr>
   </table>
  </UserNameTemplate>
</asp:PasswordRecovery>

Reset Password (Pane Four)

This is a custom control that I have built myself, using the CAPTCHA image that I set up in another article.

If you want to use this then use the code below, we will add the VB later.

HTML

<div style="text-align: center">
  <asp:Label ID="Label2" runat="server" Text="Please enter your email address"></asp:Label>
</div>
<div style="text-align: center">
  <asp:textbox ID="UserName" runat="server" Width="300px"></asp:textbox>
</div>
<div style="text-align: center">
  <img id="captchaImage" src="" alt="" runat="server"/>
  <asp:HiddenField ID="CaptchaGuid" runat="server" />
</div>
<div style="text-align: center">
  <asp:Label ID="Label1" runat="server" Text="Please enter the value in the image above"></asp:Label>
</div>
<div style="text-align: center">
  <asp:textbox ID="CaptchaVal" runat="server" Width="200px"></asp:textbox>
</div>
<div style="text-align: center">
  <asp:Button ID="CaptchaSubmit" runat="server" Text="Reset my Password" />
</div>
<div style="text-align: center">
  <asp:Label ID="CaptchaResult" runat="server" Text="Please enter the value in the image above"></asp:Label>
</div>

Web Config Changes

Dependent on how you want to run your system, you will need to add and modify the following to your web config file.

Where I have put YourAppName you can change all instances to something more suitable. There are two Membership providers, the second is only used in Resetting a password.

XML

<configuration>
  <connectionStrings>
  <add  name="MySqlConnection" connectionString="Data Source=.;Initial Catalog=dbname;Integrated Security=True"  providerName="System.Data.SqlClient" />
  </connectionStrings>
  <system.web>
  <authentication  mode="Forms">
  <forms loginUrl="Login" name=".ASPXFORMSAUTH" defaultUrl="Home"/>
  </authentication>
  <membership  defaultProvider="SqlProvider" userIsOnlineTimeWindow="30">
  <providers>
  <clear/>
  <add  name="SqlProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="MySqlConnection" applicationName="YourAppName" enablePasswordRetrieval="false" enablePasswordReset="true" minRequiredPasswordLength="6"  minRequiredNonalphanumericCharacters="0"  requiresQuestionAndAnswer="true" requiresUniqueEmail="true" passwordFormat="Hashed"/>
  <add  name="SqlProviderReset" type="System.Web.Security.SqlMembershipProvider" connectionStringName="MySqlConnection" applicationName="YourAppName" enablePasswordRetrieval="false" enablePasswordReset="true" minRequiredPasswordLength="6"  minRequiredNonalphanumericCharacters="0"  requiresQuestionAndAnswer="false" requiresUniqueEmail="true" passwordFormat="Hashed"/>
  </providers>
  </membership>
  <profile>
  <providers>
  <clear/>
  <add  name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="YourAppName"/>
  </providers>
  </profile>
  <roleManager  defaultProvider="SqlProvider" enabled="true" cacheRolesInCookie="true" cookieName=".gsclaytonnet" cookieTimeout="30" cookiePath="/" cookieRequireSSL="false" cookieSlidingExpiration="true" cookieProtection="All">
  <providers>
  <add  name="SqlProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="MySqlConnection" applicationName="YourAppName"/>
  </providers>
  </roleManager>
  </system.web>
  <system.net>
  <mailSettings>
  <smtp  deliveryMethod="Network" from="info@gsclayton.net">
  <network  host="smtp setting" userName="info@email" password="Password" port="Port" />
  </smtp>
  </mailSettings>
  </system.net>
  <system.serviceModel>
  <behaviors>
  <endpointBehaviors>
  <behavior  name="GSClayton.CaptchaAspNetAjaxBehavior">
  <enableWebScript />
  </behavior>
  </endpointBehaviors>
  </behaviors>
  <serviceHostingEnvironment  aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  <services>
  <service name="GSClayton.Captcha">
  <endpoint address="" behaviorConfiguration="GSClayton.CaptchaAspNetAjaxBehavior" binding="webHttpBinding" contract="GSClayton.Captcha" />
  </service>
  </services>
  </system.serviceModel>
</configuration>

Load and Other Variables

Modification will be required to this code, the meta data will need changing. You can uncomment the code to redirect to HTTPS if you have an SSL license. It also utilises View State to remember the last page the user was on.

VB

Imports System.Data.SqlClient
Imports System.Net.Mail
 Inherits System.Web.UI.Page
 Dim con As New SqlConnection(ConfigurationManager.ConnectionStrings("MySqlConnection").ConnectionString)
 Protected Sub Page_Load() Handles Me.Load
  Page.Title = "gsclayton.net Sign in"
  Page.MetaKeywords = "gsclayton.net, Databases, Web Design, SQL, HTML, .NET, ASP, CSS, Home"
  Page.MetaDescription = "gsclayton.net Databases and Web Design, SQL, HTML, .NET, ASP, CSS, Home"
 If Not IsPostBack Then
 If Not IsNothing(Request.UrlReferrer) Then
  ViewState("PreviousPageUrl") = Request.UrlReferrer.ToString()
 End If
 End If
 If Request.IsSecureConnection = False Then
 Dim uri As New UriBuilder(Page.Request.Url)
  uri.Scheme = System.Uri.UriSchemeHttps 'To redirect to https, uncomment below
 'Response.Redirect(uri.ToString())
 End If
 Dim com As New SqlCommand("EXEC NewCaptchaValue", con)
  con.Open()
 Dim dr = com.ExecuteReader
 Dim valStr As String
 Dim valGUID As String = "8375DFCA-7499-4756-A470-CE176C44A02A"
 While dr.Read
  valGUID = dr.Item(0).ToString()
  valStr = dr.Item(0).ToString()
   End While
  CaptchaGuid.Value = valGUID
  captchaImage.Src = ResolveUrl("https://www.gsclayton.net/captcha/" + valGUID)
  con.Close()
End Sub

Logging In

If you have created your own control then double check this code, however it should plug straight in. I found the remember me on the following link.

VB

Private Sub LoginButton_Click(ByVal sender As ObjectByVal e As System.EventArgsHandles Login1.LoggedIn
 Dim PreviousPageUrl As String = ViewState("PreviousPageUrl")
 If Not PreviousPageUrl Is  Nothing Then
 Dim uri As New UriBuilder(PreviousPageUrl)
  uri.Scheme = System.Uri.UriSchemeHttp ' Redirect to https
  PreviousPageUrl = uri.ToString
 Else
  PreviousPageUrl = "home"
 End If
 If Login1.RememberMeSet = True Then
  Response.Cookies.Clear()
 'Thanks to for his article-http://blogs.msdn.com/b/friis/archive/2010/08/18/remember-me-checkbox-does-not-work-with-forms-authentication.aspx
 Dim ticket As New  FormsAuthenticationTicket(Login1.UserName, True, 36000) '25 days, nice round number
 Dim ticketEnc As String = FormsAuthentication.Encrypt(ticket)
 Dim AuthCookie As HttpCookie = New HttpCookie(FormsAuthentication.FormsCookieName, ticketEnc)
  AuthCookie.Expires = ticket.Expiration
  Response.Cookies.Add(AuthCookie)
  ViewState("PreviousPageUrl") = Nothing
  Response.Redirect(ResolveUrl(PreviousPageUrl))
 Else
  ViewState("PreviousPageUrl") = Nothing
  Response.Redirect(ResolveUrl(PreviousPageUrl))
 End If
End
 Sub

Creating a User

This code should also plug straight in, it will redirect the user to the last URL they were on.

VB

Protected Sub CreateUserWizard1_CreatedUser(sender As Object, e As System.EventArgsHandlesCreateUserWizard1.CreatedUser
 Roles.AddUserToRole(CreateUserWizard1.UserName.ToString, "User")
 'Send email etc if desired
 Dim PreviousPageUrl As String = ViewState("PreviousPageUrl")
 If Not PreviousPageUrl Is  Nothing Then
 Dim uri As New UriBuilder(PreviousPageUrl)
  uri.Scheme = System.Uri.UriSchemeHttp ' Redirect to https
  PreviousPageUrl = uri.ToString
 Else
  PreviousPageUrl = "home"
 End If
  ViewState("PreviousPageUrl") = Nothing
  Response.Redirect(ResolveUrl(PreviousPageUrl))
End Sub

Sending a new password

The last sub to add will check the Captcha value, if correct it will send an email with a new randomly generated password, this is why we added a secondary provider.

VB

Protected Sub CaptchaValue() Handles CaptchaSubmit.Click
  Try
  Dim valStr As String = Replace(CaptchaVal.Text.ToString, "'""''")
  Dim valGUID As String = Replace(CaptchaGuid.Value, "'""''")
  Dim valEmail As String = Replace(UserName.Text.ToString, "'", "''")
  Dim com As New SqlCommand("EXEC CaptchaValue '" & valGUID & "','" & valStr & "'", con)
  Dim comu As New SqlCommand("SELECT UserName FROM aspnet_Membership m INNER JOIN aspnet_Users u ON u.UserId=m.UserId WHERE Email='" & valEmail & "'", con)
  con.Open()
  Dim Result As String = com.ExecuteScalar
  Dim GetUser As String = comu.ExecuteScalar
  con.Close()
  If Result = "Pass" And valEmail <> "" And GetUser <> "" Then
  CaptchaResult.Text = "A new password has been generated and sent to your email address"
  CaptchaResult.ForeColor = Drawing.Color.Green
  Dim mp As MembershipProvider = Membership.Providers("SqlProviderReset")
  Dim user As MembershipUser = mp.GetUser(GetUser, False)
   Dim pass As String = user.ResetPassword
  Dim MailMessage As New MailMessage()
  MailMessage.From = New MailAddress("info@gsclayton.net")
  MailMessage.To.Add(New MailAddress(valEmail))
  MailMessage.IsBodyHtml = True
  MailMessage.Subject = "Your New Password"
  Dim MailClient As New SmtpClient()
  'HTML Format
  Dim HTMLViewString As String = "<div>We have reset your password <br /> " +
  "User Name: " + GetUser + " <br /> " +
  "Password: " + pass + "<br /> " +
  "You should change your password on the following <a href=""https://www.gsclayton.net/"">Link</a></div>"
  MailMessage.Body = HTMLViewString
  MailClient.Send(MailMessage)
  Else
  CaptchaResult.Text = "Please enter the value correctly and and your email address"
  CaptchaResult.ForeColor = Drawing.Color.Red
  End If
  Catch
  CaptchaResult.Text = "Please enter the value correctly and and your email address"
  CaptchaResult.ForeColor = Drawing.Color.Red
  End Try
End Sub
Ousia Logo