In this project, we’ll take an existing conference application written in ASP.NET Core and implement several simple security measures to help improve application security.
If you want to use Visual Studio (highly recommended) follow the following steps:
- If you already have Visual Studio installed make sure you have .NET Core installed by running the "Visual Studio Installer" and making sure ".NET Core cross-platform development" is checked.
- If you need to install Visual Studio download it at https://visualstudio.microsoft.com/ by selecting "Community 2019" from the "Dowload Visual Studio" drop down list. (If you're using Windows you'll want to check "ASP.NET" and ".NET Core cross-platform development" on the workloads screen during installation.)
- Open the .sln file in Visual Studio.
- To run the application simply press the Start Debug button (green arrow) or press F5.
- If you're using Visual Studio on Windows, to run tests open the Test menu, click Run, then click on Run all tests. (results will show up in the Test Explorer)
- If you're using Visual Studio on macOS, to run tests select the ConferenceTrackerTests Project, then go to the Run menu, then click on Run Unit Tests. (results will show up in the Unit Tests panel)
(Note: All tests should fail at this point. This is by design. As you progress through the project, more and more tests will pass. All tests should pass upon completion of the project.)
If you would rather use something other than Visual Studio:
- Install the .NET Core SDK from https://dotnet.microsoft.com/download once that installation completes, you're ready to get started!
- To run the application go into the ConferenceTracker project folder and type
dotnet run. - To run the tests go into the ConferenceTrackerTests project folder and type
dotnet test.
- Require HTTPS (What is HTTPS? https://en.wikipedia.org/wiki/HTTPS)
- Utilize HSTS (What is HSTS? https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security)
- Restrict cross origin requests
- Setup basic error handling
- Setup basic logging
- Store and consume user secrets
- Set and configure cookie policy options
Note: This isn't the only way to implement this project. However, this is what the project's tests are expecting. Implementing the features in a different way will likely result in being marked as incomplete / incorrect.
In order to properly secure any web application, HTTPS's end to end encryption has become effectively a mandatory to help prevent "man in the middle" attacks.
- Redirect users who access the web application through
HTTPto useHTTPSinstead- In
Startupclass'sConfiguremethod, call theUseHttpsRedirectionmethod onapp. (This should be done after our database is created, but beforeUseStaticFilesis called.)
- In
To help prevent "protocol downgrade" and "cookie hijacking" attacks we can utilize HTTP Strict-Transport-Security (HSTS). This should be limited to only happen in production.
- Setup the web application to use the HSTS header
- In
Startupclass'sConfiguremethod, as the very first thing we do in that method, call theIsDevelopmentmethod onenv, and check if it returnstrueorfalse.- If
false, callUseHstsonapp.
- If
- In
We need the ability to see when something has gone wrong in detail as a developer, while also ensuring we don't accidentally expose that information to our users.
- Setup separate error pages for developers and users
- In
Startupclass'sConfiguremethod, ifIsDevelopmentreturns:falsecall theUseExceptionHandlermethod onappwith an argument of"/Home/Error"before the call toUseHsts.truecall theUseDeveloperExceptionPagemethod onappand theUseDatabaseErrorPagemethod onapp.
- In
The best way to prevent "Cross Orgin" attacks is to not use Cross-Origin Resourc Sharing (CORS). However this isn't always an option: in such cases we can specify which domains to allow requests.
- Setup CORS to permit requests from a single domain
- In our
Startupclass, create a newprivatereadonlyfield of typestringnamed_allowedOrigins, and set it to the value"_allowedOrigins". - In our
Startupclass'sConfigureServicesmethod, add a call to the methodAddCorsonservicesand provide it an argument ofoptions => { options.AddPolicy(_allowedOrigins, builder => { builder.WithOrigins("http://pluralsight.com"); }); }. (This is specifying the name of our CORS policy, and providing what domains will be permitted) - In our
Startupclass'sConfiguremethod, add a call toUseCorsonappwith_allowedOriginsas the arguement. Do this before our call toUseHttpsRedirection.
- In our
Logging is an important strategy for diagnosing bugs, spotting attempts to compromise security, as well as investigating what happened after something has gone wrong.
Note: Logging methods are not asynchronous, nor should they be as logging asynchronously can cause logged messages to be out of order.
Note: Logging is enabled by default and provides Console, Debug, EventSource, and EventLog logging. EventLog is a Windows feature, and on other operating systems EventLog logging will be ignored.
-
Add logging to our
Startupclass- Update our
Startupclass'sConfiguremethod, to log if the application is running in development.- Add a using directive for
Microsoft.Extensions.Logging. - Update the
Configuremethod's signature to take a third parameter of typeILogger<Startup>with a name oflogger. - In our existing condition that checks if
IsDevelopmentistrue, whentruecallLogInformationonloggerwith an argument of"Environment is in development"before our exception handling.
- Add a using directive for
- Update our
-
Add logging to our
PresentationsControllerclass- In our
PresentationsControllerclass, add aprivatereadonlyfield of typeILoggernamed_logger. - Update our
PresentationsControllerupdate the constructor to take a third parameter of typeILogger<PresentationsController>namedloggerand set_loggertologger.
We'll add logging to see when our
Editaction is called, and what outcome occurred via logging. Note some of this logging will duplicate built in logging. We will not add logging to everything. This is simply to familiarize yourself with logging in a controller action.- Update our
PresentationsController'sEditmethod (theHTTP Getnot theHTTP PostEditmethod), add the following logging.- As the very first line call
LogInformationon_loggerwith a message"Getting presentation id:" + id + " for edit.". - Inside the condition where we check if
idisnull, before we returnNotFound(), callLogErroron_loggerwith a message"Presentation id was null.". - Inside the condition where we check if
presentationisnull, before we returnNotFound(), callLogWarningon_loggerwith a message"Presentation id," + id + ", was not found.". - Immediately before we set our
ViewData, callLogInformationon_loggerwith a message"Presentation id," + id + ", was found. Returning 'Edit view'.
- As the very first line call
- In our
We need to be very careful handling sensitive information like connection strings. We absolutely DO NOT want these in our code base where they could accidently be committed to our repository potentially compromising our database credentials. Instead, locally we can use UserSecrets and in production EnvirornmentVariables to handle this information securely.
First we need to set up UserSecrets and EnvironmentVariables, as of ASP.NET Core 2.0 that's just done by default when CreateDefaultBuilder is called in our Program class! So, we can skip to just putting them to use!
- Add a call to retrieve
SecretMessagefromConfiguration- Inside our
Startupclass'sConfigureServicesmethod, set ourSecretMessageproperty using to the returned value fromConfiguration["SecretMessage"]. Normally, you'd use this to contain things like connection strings. However, since we're using anInMemorydatabase, this is simply being used as an example, and serves no functional purpose.
- Inside our
This is how you set user secrets. However, because they're a secret only stored on your local computer, we can't actually check to see if you did it right. Which is a good thing... but... means we can't tell you if it worked :)
- Create a "Password" secret
- We're going to use the .NET Core CLI (Command Line Interface).
- In the CLI navigate to the
ConferenceTrackerdirectory, not the solution's directory. (You can use thecdcommand to navigate between directories. Example:cd ConferenceTracker) - Enter the command
dotnet user-secrets init- this sets the
secretsIdfor your project
- this sets the
- Enter the command
dotnet user-secrets set "SecretMessage" "Keep it secret, Keep it safe."- this sets a secret with the key
"SecretMessage"with a value "Keep it secret, Keep it safe."
- this sets a secret with the key
While not directly security related, we do need to ensure we're complying with General Data Protection Regulation (GDPR) legislation if our website is accessible to Europe. From the web application perspective this means we need to set our cookie policy.
- Add
CookiePolicyto ourStartupclass to get user consent for any cookies we might use- Inside
Startupclass'sConfigureServicesmethod, anywhere before our call toAddControllersWithViews, callConfigure<CookiePolicyOptions>onserviceswith the argumentoptions => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }. (Don't worry about specifically what these arguments mean. You'll need to change them based on what cookies you use and what they store) - Inside
Startupclass'sConfiguremethod, anywhere before our call toUseRouting, callUseCookiePolicyonapp.
- Inside
You've completed the tasks of this project. Congratulations! If you want to continue working on this project, some next steps would be to expand the existing functionality of the application to include an administration section to manage your existing users, implement caching, and make the information stored in the application available via a secure API.
Otherwise now is a good time to continue the ASP.NET Core path to expand your understanding of the ASP.NET Core framework, or delve into the C# path to expand your knowledge of the C# programming language.