Application Page Code to Further Secure Forms Authentication
In the form login button click event for an ASP.NET Web application, the developer should limit the number of tries for a login to a pre-determined limit, and then lock out the user account in the database. The programmer should validate the user name and password at the client with JavaScript validators, and then validate a second time with server-side code business logic. Regular expressions are a common forms of both client-side and server-side validation methods. Limiting a user name or password textbox with the maximum limit property also will perform a type of string size validation. The login button click event might contain:
protected void btnSignin_Click(object sender, EventArgs e)
{
/// <summary>
/// Count number of login tries and could lockout if too many.
/// Clean both password and user id then compare to decrypted
/// password in login database. Stored login info in database.
/// If authenticated, then encrypt cookie for forms authentication.
/// Save cookie, and if times out, user must login again.
/// </summary>
numLoginTries = numLoginTries + 1;
if (numLoginTries >= 4)
this.txtUserName.Text = "";
this.txtPassword.Text = "";
//put in code here to lock out user from logging into database again if needed.
//put in code here to notify security web service of violation if applicable
this.lblMessage.Text = "You have exceeded the number of allowable login tries. You are now locked out of this application.";
return;
}//if (numLoginTries >= 4)
else
// Attempt to Authenticate User Credentials using encrypted passwords
string passWord = string.Empty;
//Clean the RacfID and Password strings before passing on
string userRacfId = CleanStringRegex(this.txtUserName.Text.Trim());
string UserPassword = CleanStringRegex(this.txtPassword.Text.Trim());
//validate clean strings for racfId and password character/number formats/allowable special characters
if (ValidateUserAndPassword(userRacfId, userPassword) == true)
//User name and password validated at Client and then Business Object
//Now fetch encrypted password by RACFID, decrypt, and compare in
//Business Object
try
//Use a hash of the password with a salt value and compare hashes.
userObject = Logon(userDS, userRacfId, userPassword);
//If results returned, then login valid
if (userObject.IsValid == true)
//Login valid, so now make audit log login entry
if (MakeAuditLogEntry(userRacfId.Trim()) == false)
this.lblMessage.Text = "Unable to log in, please try again later.";
}
//set up for cookie encryption
string encryptedTicket = "";
string userData = "WhatEverUserDataNeededHere";
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
userRacfId.Trim(),
System.DateTime.Now,
System.DateTime.Now.AddMinutes(60),
false,
userData,
FormsAuthentication.FormsCookiePath);
encryptedTicket = FormsAuthentication.Encrypt(ticket);
// Use security system to set the UserID within a client-side Cookie
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket));
//Redirect browser to MainMenu.aspx
FormsAuthentication.RedirectFromLoginPage(userRacfId.Trim(), false);
//if rows not returned, then login not valid, prompt with Login Failed
this.lblMessage.Text = userMessage.Trim() + " <" + "br" + ">Login Failed!" + "<" + "br" + ">";
} //try
catch (Exception g)
if (userMessage != "OK")
//In this application, user message inserted
//from business object, and real error goes to
//Exception handler Web or WCF Service
ShowMessageBox(this.Page, userMessage);
//ShowMessageBox is a JavaScript alert box
//put BLL save app error here.
string sError = ReturnBLLErrorDescription(g);
HandleBPAppErrors(1, "Client.btnSearch_Click", userRacfId.Trim(), sError);
userMessage = "An error has occurred and has been reported";
ShowMessageBox(this.Page, userMessage.Trim());
//You can also display error message on form label by uncommenting the code below
//this.lblError.Text = sError;
}// else if (numLoginTries >= 4)
In the form initialization and load events, use Forms Authentication and encrypted tickets in the cookie to prevent unauthorized access to forms before they query the database and present sensitive data to the client browser. For example, in the “Page_init” event, verify that the page request is from an authenticated user with code like the following:
// Check whether the current request has been
// authenticated. If it has not, redirect the
// user to the Login.aspx page.
if (!Request.IsAuthenticated)
Response.Redirect("Login.aspx", false);
If it has not been authenticated, the request is redirected to a login page where users can enter their user name and password credentials into the Web application.
If you load JavaScript code from the “Page_init” event of an ASP.NET form, further security can be assured by first checking to see if the user has been authenticated:
//Check to make sure that user is authenticated
//before loading the JavaScript -- this prevents
//unauthorized viewing or misuse of data on a form
if (User.Identity.IsAuthenticated)
//Resets the Drop Down List in Dialog Window
GenerateResetDDLScript();
//Adds a function to the drop down list to display a print, Excel, and
//Word dialog Window based on a selected index
GenerateDisplayPrintDialogScript();
this.ddlActions.Attributes.Add("onchange", "Print(this)");
Response.Redirect("NoAccess.aspx");
The JavaScript should be placed under the .NET Common Language Runtime (CLR) managed code control by injecting the script with the ClientScript class into the HTML markup code sent to the client browser.
#region Print Grid
//---------------------------------------------------------------------------
/// Method Name: GenerateDisplayPrintDialogScript
/// Description: Loads javascript to perform printing of grid.
public void GenerateDisplayPrintDialogScript()
string jScript = string.Empty;
jScript = "<script language='javascript' type='text/javascript'>function Print(ddl)" +
"{ " +
" if (ddl.selectedIndex == 1) " +
" {" +
" PagingGridView1.print(); " +
" ResetDDL(); " +
" } " +
" else if (ddl.selectedIndex == 2)" +
" { " +
" PagingGridView1.exportToExcel('UserList', true, true, false, true);" +
" else if (ddl.selectedIndex == 3)" +
" PagingGridView1.exportToWord('UserList', true, true, false, true);" +
"} " +
"</script>";
if (!ClientScript.IsClientScriptBlockRegistered("PrintScript"))
ClientScript.RegisterClientScriptBlock(this.GetType(), "PrintScript", jScript);
/// Method Name: GenerateResetDDLScript
/// Description: Resets the DDL back to the zero index when action completed.
public void GenerateResetDDLScript()
jScript = "<script language='javascript' type='text/javascript'>function ResetDDL()" +
" document.getElementById('" + this.ddlActions.ClientID + "').selectedIndex = 0; " +
"}" +
if (!ClientScript.IsClientScriptBlockRegistered("ResetDDLScript"))
ClientScript.RegisterClientScriptBlock(this.GetType(), "ResetDDLScript", jScript);
#endregion Print Grid
The Client Script Manager Class (ClientScript) can also be utilized to register JavaScript files in the same manner as the script strings above. The advantage of utilizing a JavaScript file is that breakpoints can be set in it to allow code debugging stop and restart breakpoints.
The first task in preventing unauthorized use of sensitive resources in an ASP.NET application that uses Internet Information Services (IIS) is to restrict access. ASP.NET developers restrict unauthorized access by utilizing ASP.NET and IIS authentication techniques. For Intranet Web applications, the developer could choose Windows or Forms Authentication, but for Internet ASP.NET security, Forms Authentication is the most used alternative.
Using Windows Authentication
For Intranets, the ASP.NET programmer can set up Windows Authentication in the main solution Web.config file in a project that contains the Web application user startup and processing forms in the following manner:
<configuration>
<system.web>
<authentication mode="Windows" />
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
Note that the <deny users=”?”/> directive within the <authorization> section above is what tells ASP.NET to deny access to the application to all “anonymous” users to the site (the “?” character means anonymous user). This forces Windows to authenticate the user, and ensures that the username is always available from code on the server.
Clients then simply log into Active Directory eliminating the need for a login form, and unlike Forms Authentication, users need not manually enter their username/password credentials to login to the application. Instead, ASP.NET and IIS can automatically retrieve and validate the Windows username of the end-user visiting the site in a secure manner.
If the programmer wants to access the logged-in username and role/group mappings for the authenticated user within ASP.NET form code-behind, the follow C# code will accomplish this task:
string userName;
userName = User.Identity.Name;
To gain access to user information from within a business or data access layer class, a developer need only write C# code like the following to achieve the same result as above:
System.Security.Principal.IPrincipal user;
user = System.Web.HttpContext.Current.User;
userName = user.Identity.Name;
An ASP.NET Intranet application can access the current Windows authenticated user role/group mappings with the following C# code:
if(user.IsInRole(@"DOMAIN\managers"))
this.lblRole.Text = user.Identity.Name + " is in the manager role.";
this.lblRole.Text = user.Identity.Name + " is not in the manager role.";
Using Forms Authentication
Forms authentication uses an authentication ticket that is created when a user logs on to a site, and then it tracks the user throughout the site. The forms authentication ticket is usually contained inside a cookie that should be encrypted. Forms authentication should be combined with the IIS anonymous authentication option.
If IIS is configured to use anonymous authentication, IIS authenticates the user and creates a Windows token to represent the user. IIS determines the authentication mode that it should use for a particular application by looking at IIS metabase settings. A token for the IUSR_MACHINE account is generated and then used to represent the anonymous user. IIS-then passes the token to ASP.NET running on IIS, and its runtime determines the Forms Authentication setup from the application Web.config file. Typically, a Web.config file setup for Forms Authentication would contain:
<!--
The <authentication> section enables configuration
of the security authentication mode used by
ASP.NET to identify an incoming user.
-->
<machineKey validationKey="123FFF8CF7F466FB253AF78C5AFC96A8CC874B2A9F3DF662F0DE3A57A62486FDF253397E3BE61A3098EA7A6543E8407661DD2A5DA009840A7EE2DA5EA99B7F010" decryptionKey="B715956BF9F134BF9E6771E610C237DD1DA49CBE345B2BB1F0413C630D1B5822" validation="SHA1" decryption="AES"/>
<authentication mode="Forms">
<forms
cookieless="UseCookies"
loginUrl="http://YourServer/Login/Login.aspx"
name=".YourWebApp001"
timeout="20"
path="/"
protection="All"
slidingExpiration="false"
enableCrossAppRedirects="false">
</forms>
</authentication>
<customErrors mode="On" defaultRedirect="~/Errors/DefaultError.aspx">
<!-- Set httpCookies to true to make cookies accessible only by the server.
This is one of the defenses against Cross-Site Scripting attacks (CSS/XSS).
<httpCookies httpOnlyCookies="true"/>
The attribute values used above are explained below:
IIS defaults anonymous access to “enabled” for all applications that use Forms Authentication. However, ASP.NET can utilize the UrlAuthorizationModule class to help ensure that only authenticated users can access a page. The programmer can configure the UrlAuthorizationModule class properties by using the ”authorization” element as shown above in the Web.config file XML. The above code markup denies access to all anonymous users (deny users = “?”).
The “customErrors” mode set to “on” redirects the user to the error page path setting as show above in the Web.config example XML code. Having a generic error page with user-friendly messages provides the client with helpful information, but this page should not give the unauthorized person any useful hacking tips for accessing application resources.
Internet Explorer has support since version 6 SP1 for a cookie property called “httpOnlyCookies” that can help mitigate cross-site scripting threats that result in stolen cookies. As shown above in the Web.config example, when a cookie that has “httpOnlyCookies” set to “true” is received by a compliant browser, it is accessible only to the Web server and inaccessible to client-side scripts.
You could write the XML comments in your project classes by:
1. Go to the properties page in the document.2. Highlight the Build choice in the left hand menu item list3. On the very bottom of the Build page, you will see Output.4. Underneath Output, click on the "XML documentation file" checkbox.5. You will see a path to the bin folder like: "bin\Release\AppBase.XML"6. Build (compile) the project.7. Go to your Solution Explorer and click on the Show All Files button.8. Look under the bin\Release flolder, and you will see the XML you just created.9. Double click on it, and you can read the file.
You would end up with the following in an XML file under your bin directory: <member name="T:DHS.AppBase.BP"> Namespace: DHS.AppBase Authors: FS/KW/JW Date: 4/22/2006 Finished properties and methods Updated: 4/23/2006 Added comments and header page Updated: 12/25/2006 Added DHS.AppBase Updated: 3/21/2007 Added Javascript Client Code <summary> Purpose: Parent Base Class for specific section page code-behinds. Works in conjuction with ASP.NET pages as parent for code behind page conrollers to contain common properties, methods, cascading styles sheets or skins, and javascript and XML calls to Business web services through the Domain bll. </summary> </member> You could the use comments like above in the document to compare against your functional requirements as a User Acceptance Test. Franc.
<
Namespace: DHS.AppBase
Authors: FS/KW/JW
Date: 4/22/2006 Finished properties and methods
Updated: 4/23/2006 Added comments and header page
Updated: 12/25/2006 Added DHS.AppBase
Updated: 3/21/2007 Added Javascript Client Code
Purpose: Parent Base Class for specific section page code-behinds.
Works in conjuction with ASP.NET pages as parent for code behind page
conrollers to contain common properties, methods, cascading
styles sheets or skins, and javascript and XML calls to
Business web services through the Domain bll.
</
You could the use comments like above in the document to compare against your functional requirements as a User Acceptance Test.
Franc.
Table Names
· The table name must clearly identify the collection of data the table represents or contains: e.g., UserAddresses.
· Table names are the only database objects not prefixed since they often represent objects. Most standards call for tables to be plural like a collection of row objects similar to instances of a class: e.g., Clients, Appeals. However, this guideline is not static, and the DBA can make exceptions.
· Only the first letter of each whole word in a table name should be capitalized: e.g., LegalContacts. ProviderAddresses.
· Functional table names (for many-to-many relationships) should be a combination of the two tables referenced. For example, cross-reference tables for the many-to many relationships between Clients and Appeals: ClientsAppeals.
· Lookup tables should be grouped together. They can be grouped at the beginning by the prefix ‘lkup_’; e.g., lkup_NamePrefixes.
Column Names
· Each column name must be derived from the business name identified during the business/data analysis process. If the column was not identified during the analysis of business data, it must still be given a spelled-out logical name; e.g., ClientFirstName, ClientMiddleInitial, ClientPhoneAreaCode, etc.
· Column names should be descriptive and clearly understandable by someone who looks at them for the first time.
· Each word in a column name is capitalized, and, for the sake of clarity, but they can be separated by an underscore if it clarifies the name: e.g.,
· Column names should contain letters, numbers, and underscore characters (“_”) only: e.g., ClientAppealHistory_2004.
· If multiple fields in a table reference the same primary key in another table, the field names should be followed by a suffix. (Examples: fk_BankID_From and fk_BankID_To)
· Column names should be unique throughout the database, if possible. The DBA can make exceptions to this guideline.
· The primary key Column should always end with “ID” and begin with “pk_”. If the Column is not auto-numbered, all relationships to that Column must have referential integrity when cascading an update.
· A Column referencing a primary key Column in another table should have exactly the same name as the Column it is referencing from the reference table plus “fk_” as a prefix to denote foreign key. E.g.; fk_ClientID.
· Column names should not be abbreviated. (An exception can be made by the DBA if the Column name is too long to be understood)
Stored Procedures
· All application level and user defined stored procedures are prefixed with the constant 'usp_' with a description of what the stored procedure does. Example: usp_GetLastModifiedDate.
· Inserts, Updates, and Deletes that affect table rows should be named with the prefix usp_ plus the the table name, and Insert plus Update, or Delete. E.g.; usp_Appeals_Insert.
· Actual table Deletes must be approved by the DBA since deletes are in reality only Inserts that make a table row inactive by modifying a row column.
· Use upper case for all SQL keywords: e.g., SELECT, INSERT, UPDATE, WHERE, AND, OR, LIKE, etc.
· Liberally comment code blocks not easily understood by non-technical persons.Use single-line comment markers (--) for comments and headers.
· Reserve multi-line comments (/*.. ..*/) for blocking out sections of SQL code.
· Indent the SQL code 4 spaces per line to improve readability when code blocks change: e.g., see Figure 1.
· Use single quote characters to delimit strings.
· Use parentheses to increase readability or to force order of execution: e.g., WHERE (WidgetColor=’red’ AND ((WidgetSize = 1) OR (Size = 2)).
· Use BEGIN…..END blocks when multiple statements are present within a conditional code segment: e.g., see Figure 1.
· Use one blank line to separate code sections: e.g., see Figure 1.
· Use spaces so that expressions read like sentences;
e.g., fillfactor = 25, not fillfactor=25.
· Place SET statements before any executing code in the procedure.
· Use SET NOCOUNT ON at the beginning of stored procedures: e.g., see Figure 1.
· Tables, views, and stored procedures are always fully qualified to ensure SQL Server follows a direct path to find it rather than searching through the master catalog.
· Do not call functions repeatedly within your stored procedures, triggers, functions and batches. For example, you might need the length of a string variable in many places of your procedure, but don't call the LEN function whenever it's needed. Instead, call the LEN function once, and store the result in a variable for later use.
Code Readability
· Write comments in your stored procedures, triggers and SQL batches generously; especially when the meaning of the code logic is not obvious. This helps other programmers and DBAs understand the SQL code.
· Do not use column numbers in the ORDER BY clause. In the following examples, note that the second query is more readable than the first.
Example 1:
SELECT OrderID, OrderDate FROM Orders ORDER BY 2
Example 2:
SELECT OrderID, OrderDate FROM Orders ORDER BY OrderDate.
· Use the more readable ANSI-Standard Join clauses instead of the old style joins. With ANSI joins, the WHERE clause is used only for filtering data. With older style joins, the WHERE clause handles both the join condition and filtering data. The first of the following two examples shows the old style join syntax, while the second one shows the new ANSI join syntax.
SELECT a.au_id, t.title FROM titles t, authors a, titleauthor ta
WHERE a.au_id = ta.au_id AND ta.title_id = t.title_id AND
t.title LIKE 'Computer%'
SELECT a.au_id, t.title FROM authors a INNER JOIN titleauthor ta ON a.au_id = ta.au_id
INNER JOIN titles t ON ta.title_id = t.title_id
WHERE t.title LIKE 'Computer%'
· Do not use SELECT *; always list all fields by name in the SELECT statement.
· Avoid unnecessary use of temporary tables; use 'Derived tables' or CTE (Common Table Expressions) wherever possible, as they perform better.
· To make SQL statements more readable, start each clause on a new line and indent when needed.
· Do not use GOTO as this is bad coding by any standard.
· Check the global variable @@ERROR immediately after executing a data manipulation statement (like INSERT/UPDATE), so that you can rollback the transaction if an error occurs. However, make sure that you assign @@ERROR to a variable before using an IF statement since this will cause @@ERROR to reset, e.g.;
USE [dbo].[Test]
GO
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
-- =============================================
-- Author: Franc Stratton
-- Create date: 11/26/2006
-- Description: Adds a user login row to table
CREATE PROCEDURE [dbo].[Test].[usp_LoginInsert]
-- parameters for the stored procedure
@UserID char(7) = 0
AS
DECLARE @Error int -- local error holder
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
BEGIN TRAN
INSERT INTO Login( UserID, LoginDateTime) values ( @UserID, GetDate())
--Assign @@ERROR to a declared @Error integer variable.
SELECT @Error = @@ERROR
--Check for ERROR with an IF, if no ERROR return row IDENTITY integer
--ELSE ROLLBACK & return -1 to signify that ERROR prevented INSERT
IF (@Error = 0)
BEGIN
COMMIT TRAN
SELECT SCOPE_IDENTITY() as 'IDENTITY'
END
ELSE
ROLLBACK TRAN
SELECT -1 as 'IDENTITY'
Figure 1: A Stored Procedure Example.
· Always use a column list in your INSERT, UPDATE, or SELECT statements. This helps avoid problems when the table structure changes (like adding or dropping a column).
· When executing an UPDATE or DELETE statement, use the primary key in the WHERE condition, if possible. This reduces error possibilities.
· Avoid wild-card characters at the beginning of a word while searching using the LIKE keyword since this can result in an index scan and defeat the purpose of the index.
Keys, Indexes, Triggers, Views, and Constraints
· Names of all database objects and constraints should all be prefixed with a lower case identifier
Object Type
Example
Views
vw_TableName or vw_FirstLastNames (If it is combination of more than one table)
usp_ProcedureName
Primary Keys
pk_TableName
Foreign Keys
fk_ForeignKeyName
Index
ix_TableName_ColumnName (unless it’s too long, and DBA allows abbreviations for clarity)
· Triggers are named to indicate the table they are for and the type of trigger. The purpose of the trigger is identified in the prefix to the name. All triggers should be prefixed with the letters 'tr_', letters designating the type, an underscore, and the table name. The type should be designated as 'Insert' = insert, 'Update' = update, 'Delete' = delete. Example tr_Products_Insert (Insert trigger for Products Table)
Quote
Microsoft Workflows Software applies knowledge to the manipulation data while implementing business processes and rules. The knowledge imbedded in software by developers is generally a combination of procedural and declarative knowledge. Procedural knowledge is the step-by-step activities necessary to complete a business objective or “how to” perform a task. Declarative knowledge, on the other hand, is about the relationships and rules that exist among data and objects implemented in an application to achieve a business process goal. For example, a piece of declarative knowledge would be that plane reservations made at least 21 days in advance receive a 20% discount, unless the cost of the air fare is less than $100. The date and the price share a relationship and can affect each other. Expressing this type of knowledge using a general-purpose programming language isn't difficult on a small scale, but becomes more difficult as the amount of knowledge grows. In the past, programmers transformed the knowledge into procedural code using if-then-else statements. Many software applications require an enormous amount of declarative knowledge that should be, but most often is not, imbedded in the business logic layers or workflows. Some business analysts refer to declarative knowledge as "business rules" that control the logic flow in business processes. Encoding business rules into procedural code in Windows or ASP.NET form code-behind pages makes this form of declarative knowledge harder to find, understand, and modify. Over the past decade, the software industry has developed tools and models for working with declarative knowledge. The IT industry categorizes these tools as rules engines, inference engines, and logic machines. A rules engine specializes in making declarative knowledge easier to implement, process, isolate, and modify. Windows Workflow Foundation (WF) provides such a framework to model, implement, and execute business processes and logic with a built-in rules engine.Since both declarative and procedural activity types are available, Windows Workflow offers the best of both worlds. Developers can use Sequence activities to implement procedural knowledge, and Condition, Policy, or If-Then rule activities to execute declarative knowledge.
Software applies knowledge to the manipulation data while implementing business processes and rules. The knowledge imbedded in software by developers is generally a combination of procedural and declarative knowledge. Procedural knowledge is the step-by-step activities necessary to complete a business objective or “how to” perform a task.
Declarative knowledge, on the other hand, is about the relationships and rules that exist among data and objects implemented in an application to achieve a business process goal. For example, a piece of declarative knowledge would be that plane reservations made at least 21 days in advance receive a 20% discount, unless the cost of the air fare is less than $100. The date and the price share a relationship and can affect each other. Expressing this type of knowledge using a general-purpose programming language isn't difficult on a small scale, but becomes more difficult as the amount of knowledge grows. In the past, programmers transformed the knowledge into procedural code using if-then-else statements. Many software applications require an enormous amount of declarative knowledge that should be, but most often is not, imbedded in the business logic layers or workflows. Some business analysts refer to declarative knowledge as "business rules" that control the logic flow in business processes.
Encoding business rules into procedural code in Windows or ASP.NET form code-behind pages makes this form of declarative knowledge harder to find, understand, and modify. Over the past decade, the software industry has developed tools and models for working with declarative knowledge. The IT industry categorizes these tools as rules engines, inference engines, and logic machines. A rules engine specializes in making declarative knowledge easier to implement, process, isolate, and modify. Windows Workflow Foundation (WF) provides such a framework to model, implement, and execute business processes and logic with a built-in rules engine.Since both declarative and procedural activity types are available, Windows Workflow offers the best of both worlds. Developers can use Sequence activities to implement procedural knowledge, and Condition, Policy, or If-Then rule activities to execute declarative knowledge.