DotNetNuke is an ideal place to to use as a starting point for all of your users. Frequently users need to access external applications and they are required to log into those applications again after logging into DotNetNuke. A more desired solution would be to have the users automatically logged into the external application after logging into DotNetNuke. This is known as "Single Sign On".
But how to achieve this? One solution is to use shared forms authentication. This works but has it's limitations. This article explains the various ways it can be used. The main limitation is that a complex structure of shared cookies and redirects is required when the DotNetNuke site and the external application are located in different domains.
The goal of this exercise is to create a "Single Sign On" solution that works with any domain configuration, any web browser and is secure and reliable.
The Single Sign On Solution
The solution is outlined in these steps:
-
The user logs onto the DotNetNuke site.
-
The DotNetNuke site makes a web service call to the external application and instructs the external application to create a temporary password for the user in it's database. The DotNetNuke site then creates a link to the external application with the username and temporary password as parameters.
-
The user clicks on the link navigating to the external application. The external application receives the call, and after verifying the temporary password, creates an authentication token and automatically logs the user in. The external application then replaces the temporary password in it's database with a new random one.
Create the External Application
In Visual Studio 2005 or Visual Web Developer Express create a web application that uses Forms Authentication.
If the website is a file based website, in Visual Studio, you can select Website then ASP.NET Configuration to configure a directory that is not accessible by unauthenticated users. Otherwise, you can edit the web.config file manually (see the code at the end of this article).
In this example the directory Secure is configured to not allow unauthenticated users.
Next, a table is created that holds the temporary username and password:
(This table cannot be the table that is used to store the normal username and password for the application.)
Next a web service is created that will allow the DotNetNuke site to create a temporary password for a user:
public bool SetAuthendication(string tmpMasterPassword, String username, string password)
{
bool isSuccess = false;
string MasterPassword;
string strSQL = "";
MasterPassword = WebConfigurationManager.AppSettings["MasterPassword"];
if (tmpMasterPassword == MasterPassword)
{
bool boolUserExists = UserExists(username);
if (boolUserExists)
{
strSQL = "Update SingleSignOnUsers set password = @password where username = @username";
}
else
{
strSQL = "Insert into SingleSignOnUsers ([username],[password])
values (@username, @password) ";
}
SqlCommand cmd = new SqlCommand(strSQL, new SqlConnection(GetConnectionString()));
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add(new SqlParameter("@password", password));
cmd.Parameters.Add(new SqlParameter("@username", username));
cmd.Connection.Open();
cmd.ExecuteNonQuery();
cmd.Connection.Close();
isSuccess = true;
}
return isSuccess;
}
(For added security the web service method should be called using SSL (secure socket layer))
Next, a page is created that will receive a username and password. If it finds the user in the table, it will log the user in:
protected void Page_Load(object sender, EventArgs e)
{
string username = Request.QueryString["username"].ToString();
string password = Request.QueryString["password"].ToString();
if (ISAuthendicated(username, password))
{
lblStatus.Text = "Authendication Success";
FormsAuthenticationTicket fat =
new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddYears(1), true, "");
HttpCookie cookie = new HttpCookie(".SingleSignOn");
cookie.Value = FormsAuthentication.Encrypt(fat);
cookie.Expires = fat.Expiration;
HttpContext.Current.Response.Cookies.Add(cookie);
}
else
{
hlLoggedin.Visible = false;
lblStatus.Text = "Authendication Failed";
}
//Reset the password
//Even a failed attempt will cause a new password to be created
//A hacker would be chasing a moving target
DeletePassword(username);
}
private bool ISAuthendicated(string username, string password)
{
string tmpPassword = "";
string strSQL = "Select password from SingleSignOnUsers where [username] = @username";
SqlCommand cmd = new SqlCommand(strSQL, new SqlConnection(GetConnectionString()));
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add(new SqlParameter("username", username));
cmd.Connection.Open();
SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
while (dr.Read())
{
tmpPassword = Convert.ToString(dr["password"]);
}
dr.Close();
return (tmpPassword == password & password != "");
}
private void DeletePassword(String username)
{
Random rnd = new Random();
string tmpPassword = username + rnd.Next(1000, 99999).ToString();
string strSQL = "Update SingleSignOnUsers set password =
@password where username = @username";
SqlCommand cmd = new SqlCommand(strSQL, new SqlConnection(GetConnectionString()));
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add(new SqlParameter("@password", tmpPassword));
cmd.Parameters.Add(new SqlParameter("@username", username));
cmd.Connection.Open();
cmd.ExecuteNonQuery();
cmd.Connection.Close();
}
private static string GetConnectionString()
{
return ConfigurationManager.ConnectionStrings["SingleSignOnDB"].ConnectionString;
}
(For added security the page should be called using SSL (secure socket layer))
Create The DotNetNuke Application
In the DotNetNuke website, create a web reference to the web service created in the external application:
Next, create a custom module that calls the web service and creates the temporary password and a link to the external application, passing the username and temporary password as parameters:
protected void lnkSingleSignOn_Click(object sender, EventArgs e)
{
Random rnd = new Random();
string tmpPassword = txtUsername.Text + rnd.Next(1000, 99999).ToString();
wsSingleSignOn.WebService wsSingleSignOn = new wsSingleSignOn.WebService();
bool boolResponse =
wsSingleSignOn.SetAuthendication(txtPassword.Text, txtUsername.Text, tmpPassword);
Response.Redirect(String.Format("http://localhost/singleSignOn/
SingleSignOn.aspx?username={0}&password={1}", txtUsername.Text, tmpPassword));
}
In Conclusion
The solution described provides these benefits:
-
The solution provides single sign on for any domain configuration. and is able to cross any firewall.
-
The users real password is never transmitted over the network.
-
Once a temporary password has been used it is immediately changed. If a hacker attempts to guess temporary passwords, the password is changed each time so the hacker will be chasing a "moving target".
Complete source code
Due to the permissions issues in configuring the SQL Express database when it is moved from it's original location and that the DotNetNuke module code needs the web service link configured manually before it will compile, the download code is provided only as a reference.
DotNetNuke Module
External Application
Please note: It is impossible to assist all those who would have difficulty configuring this. Please us the forums for assistance. Also, if you have any technical difficulties do not post to the blog please post to the forums.
The following companies have indicated that they have the ability to implement this solution:
http://dnnmasters.com
http://www.venexus.com