Showing posts with label authentication. Show all posts
Showing posts with label authentication. Show all posts

Creating a ReactJS To Do application with user Authentication\Authorization [ Introduction ] - part 1

This is going to be a series of posts in which I'll show how I managed to get a simple yet functional ReactJS application working. I plan to write 1 blog post per major component detailing how it works and integrates into the app.

This was my start with ReactJS and I worked in this application for almost 1 month as seen by the git commits here. I went from 0% ReactJS knowledge to to some % knowledge of how things work in this UI\presentation framework.

Motivation

The motivation to learn it is that it's mainstream nowadays. ReactJS is also used to develop mobile apps... besides that, for a job application test I had to develop a To Do application with a set of requirements.

Requirements

Programming assignment for web development candidates

You can choose any programming language and web development framework, database, and web server you like. The web application you need to build is a basic todo list application with the following requirements:

- Users can view their todo list;
- Users can add, remove, modify and delete todo entries;
- Each todo entry includes a single line of text, due date and priority;
- Users can assign priorities and due dates to the entries;
- Users can sort todo lists using due date and priority;
- Users can mark an entry as completed;
- You don't need to spend time on UI/UX design, if you do, it will be a bonus;
- Provide a RESTful API which will allow a third-party application to trigger actions on your app (same actions available in the app);
- Provide authentication and authorization service for both the app and the API;
- As complementary item to the last requirement, you should be able to create users in the system via an interface, eg a signup/register screen.

Web Tech Stack

I took the challenge! Developed a SPA - Single Page Application using ReactJS. Why not!? I also used Visual Studio Code as the IDE in tandem with the web tech stack in which I'm experienced with: C#, ASP.NET Core Web API, SQL Server, etc.

The App

The following is the app's homepage:

Figure 1 - App home page

The left side menu when clicked gives access to the Todos form and grid.


Figure 2 - Left side menu

The right side menu allows the user to login\logout and check their profile.


Figure 3 - Right side menu

In order to be able to access the Todos form\grid the user must be authenticated.

Figure 4 - Todo form + grid

Database

Everything was developed in Mac OS and as such the database that stores todo data is an SQL Server container image running in Docker.

Figure 5 - SQL Server database image running in Docker

Web API

The Web API was implemented using an ASP.NET Core Web API project. There's also a Swagger protected help page configured.

Figure 6 - Web API Swagger help page

Source code structure

The following screenshots show how the code is structured:

Figure 7 - App structure in Visual Studio Code Explorer window

- src folder holds all the source code files for the ReactJs app.

- TodoApi holds the source code files for an ASP.NET Web API project.

The ReactJS project skeleton was created\bootstrapped with Create React App as described in the README file.

Expanding the src folder we have this:

Figure 8 - ReactJS SPA app source code files

Expanding the TodoApi folder we have this:

Figure 9 - Todo ASP.NET Web API source code files

App dependencies

The ReactJS app uses the following dependencies as listed in package.json:

"dependencies": {
"@auth0/auth0-spa-js": "^1.6.3",
"@date-io/date-fns": "^1.3.13",
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
"@material-ui/pickers": "^3.2.10",
"@testing-library/jest-dom": "^5.1.1",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^8.1.0",
"axios": "^0.19.2",
"date-fns": "^2.9.0",
"formik": "^2.1.4",
"moment": "^2.24.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
"react-scripts": "^3.3.1",
"react-toastify": "^5.5.0",
"yup": "^0.28.1"
},

Git history

The git history is a nice way to remember what I did along the way:

Git History on master by all authors

Adding Auth0 placeholders...
340657a (HEAD -> master, origin/master) by Leniel Macaferi , Sat May 02 2020 (54 minutes ago)
1 file changed, 2 insertions(+), 2 deletions(-)

Small fixes...
5b98458 by Leniel Macaferi , Fri Feb 21 2020 (2 months ago)
2 files changed, 3 insertions(+), 3 deletions(-)

- Added Roles claim... it's retrieved from Auth0 access_token; - Improved footer; - Added missing toastify messages; - Improved Profile.
2cd5cd3 by Leniel Macaferi , Tue Feb 11 2020 (3 months ago)
5 files changed, 84 insertions(+), 19 deletions(-)

- Added Bearer token support to Swagger so that it's now possible to authorize with a token prior to testing the Web API endpoints; - In TodoItemsController the current user is now extracted while getting Todos so that the user can only retrieve their todos; - Added User property to TodoItem model; - Externalized getUser from Auth0 so that it can be called from stateful ReactJS components like Todo.js; - Made use of getUser() on Todo component; - Beautified Profile component.
b704348 by Leniel Macaferi , Mon Feb 10 2020 (3 months ago)
8 files changed, 122 insertions(+), 19 deletions(-)

- Added scopes and protected Get Todos Web API call with read:todos scope. https://auth0.com/docs/quickstart/backend/aspnet-core-webapi/01-authorization#validate-access-tokens - Added Profile component
13a8675 by Leniel Macaferi , Sun Feb 09 2020 (3 months ago)
13 files changed, 173 insertions(+), 62 deletions(-)

- Made Get Todos protected by using [Authorize] attribute; - Added JwtBearer support to the ASP.NET Core Web API; - Moved BrowserRouter to index.js so that the works as expected. More info here: https://stackoverflow.com/q/60123391/114029 - Externalized getTokenSilently in Auth.js to be able to pass it to axios request interceptor.
ab74c14 by Leniel Macaferi , Sat Feb 08 2020 (3 months ago)
12 files changed, 182 insertions(+), 51 deletions(-)

Added Auth0 to be able to authenticate and authorize users. https://auth0.com/
7ab2538 by Leniel Macaferi , Fri Feb 07 2020 (3 months ago)
9 files changed, 892 insertions(+), 170 deletions(-)

- Added search todo functionality; - Improved the todos table by adding Table pagination actions and made the header sticky; - Improved todo form by disabling the save button when there are errors or when the form is not dirty yet, that is, the user has not changed anything yet; - Added global error message with the help of toastify;
2d59cf4 by Leniel Macaferi , Thu Feb 06 2020 (3 months ago)
5 files changed, 190 insertions(+), 44 deletions(-)

- Commented out InMemoryDatabase in the Web API project and started using a full featured SQL Server database with UseSqlServer. Accomplished that on Mac OS side using Docker and a Developer version of SQL Server 2017; More info here: https://database.guide/how-to-install-sql-server-on-a-mac/ https://stackoverflow.com/a/60080206/114029 - Added Swagger to the Web API project; https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-3.1&tabs=visual-studio - Changed Web API default port from 7777 to 8888; - Improved global Progress bar. Set its position to absolute so that it won't push content down when it is displayed; - Moved todo related components to their own folder.
581da74 by Leniel Macaferi , Wed Feb 05 2020 (3 months ago)
16 files changed, 72 insertions(+), 29 deletions(-)

- Added cancel button to todo form. This is useful when the user is editing a todo and wants to cancel the action; - Implemented an improved version of todo form using the HOC [Higher Order Component] withFormik; this allowed passing a parent component [Todo] function down to the child component EnhancedTodoForm which is hooked to the the TodoForm component; Amazing stuff! :-) https://jaredpalmer.com/formik/docs/api/withFormik - route icons are now dynamically retrieved; - Added react-toastify libray to be able to show customized messages to user. https://github.com/fkhadra/react-toastify
469e858 by Leniel Macaferi , Tue Feb 04 2020 (3 months ago)
12 files changed, 328 insertions(+), 100 deletions(-)

- Added request and response interceptors to axios: https://github.com/axios/axios#interceptors ... this will allow a hooking place to display toasts (messages) to the user. This is one of the next thing to be added; - Added a menu tp AppBar using a Drawer component" https://material-ui.com/components/drawers/; - Started using react-router: https://github.com/ReactTraining/react-router; - routes are exported from routes.js; - Added About and Home pages.
7412ec3 by Leniel Macaferi , Mon Feb 03 2020 (3 months ago)
17 files changed, 3682 insertions(+), 3186 deletions(-)

- Got editTodo working; - Improved themes; - Removed unused npm packages.
4afd71e by Leniel Macaferi , Sat Feb 01 2020 (3 months ago)
8 files changed, 158 insertions(+), 866 deletions(-)

- Delete todo(s) done; - WIP Edit todo; - Added axiosInterceptor which handles  the animation when any Web API is called. It shows a Material-UI ; - Externalized custom styles in themes.js; - Many improvements in Todos table.
9d3e032 by Leniel Macaferi , Fri Jan 31 2020 (3 months ago)
13 files changed, 236 insertions(+), 139 deletions(-)

Handling setSelected after deletion happens...
9be189f by Leniel Macaferi , Thu Jan 30 2020 (3 months ago)
1 file changed, 8 insertions(+), 3 deletions(-)

Added delete todo functionality with a gotcha: https://stackoverflow.com/a/33846760/114029
5ad3cbd by Leniel Macaferi , Thu Jan 30 2020 (3 months ago)
7 files changed, 77 insertions(+), 52 deletions(-)

Added material UI Table component to be able to better visualize and operate on Todos. https://material-ui.com/components/tables/
febb4b3 by Leniel Macaferi , Thu Jan 30 2020 (3 months ago)
3 files changed, 371 insertions(+), 17 deletions(-)

- Created a global theme with createMuiTheme; - Improved AppBar - Got addTodo POST call to Web API working and TodoList updating as expected.
6f8a5fb by Leniel Macaferi , Thu Jan 30 2020 (3 months ago)
8 files changed, 700 insertions(+), 174 deletions(-)

Styled App structure and TodoForm with Material UI components.
38f169d by Leniel Macaferi , Wed Jan 29 2020 (3 months ago)
8 files changed, 357 insertions(+), 196 deletions(-)

Got Formik form fields working with material-ui https://material-ui.com/.
b309899 by Leniel Macaferi , Wed Jan 29 2020 (3 months ago)
10 files changed, 1027 insertions(+), 246 deletions(-)

- Added formik, yup and react-formik-ui. - Created a Todo form with formik.
bd8b355 by Leniel Macaferi , Mon Jan 27 2020 (3 months ago)
7 files changed, 771 insertions(+), 60 deletions(-)

Started integrating UI with Web API...
614c68e by Leniel Macaferi , Sun Jan 26 2020 (3 months ago)
9 files changed, 243 insertions(+), 83 deletions(-)

- Added ASP.NET Core 3.1 Todo Web API https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-3.1&tabs=visual-studio-code
06ce6d3 by Leniel Macaferi , Sun Jan 26 2020 (3 months ago)
15 files changed, 409 insertions(+), 4 deletions(-)

- Added handleChange event handler to todo-item component; - Handled todo complete state with setState; - Added Bootstrap: https://getbootstrap.com/
c15ff51 by Leniel Macaferi , Sat Jan 25 2020 (3 months ago)
8 files changed, 147 insertions(+), 38 deletions(-)

Converted some functional components to ES6 classes.
64dad12 by Leniel Macaferi , Sat Jan 25 2020 (3 months ago)
3 files changed, 53 insertions(+), 30 deletions(-)

- Got todo items displayed from a todo list JSON file. - Used array.map to create a React TodoItem component dynamically avoiding code repetition.
84cf3fc by Leniel Macaferi , Sat Jan 25 2020 (3 months ago)
5 files changed, 94 insertions(+), 25 deletions(-)

Created basic components and applied some styles including dynamic styling with JavaScript.
90ac727 by Leniel Macaferi , Sat Jan 25 2020 (3 months ago)
6 files changed, 128 insertions(+), 18 deletions(-)

Added .eslintrc.json and modified app name.
f1953ac by Leniel Macaferi , Fri Jan 24 2020 (3 months ago)
4 files changed, 14480 insertions(+), 1 deletion(-)

Initial commit from Create React App
04291f4 by Leniel Macaferi , Fri Jan 24 2020 (3 months ago)
18 files changed, 9789 insertions(+)

Source code

GitHub repository: https://github.com/leniel/ReactToDo


Figure 10 - GitHub repo with % of source code by type


Next

So stay tuned because in the next part I'll start covering the components in a top down approach, that is, we'll start looking at the index.js file where everything gets hooked up.


User Password Expired filter attribute in ASP.NET MVC

A recent set of requirements I’ve been playing with deals with passwords. This one specifically handles password expiration.

Given that I’m working with ASP.NET MVC I know I can rest assured that there’s some great (read awesome) way of implementing a given requirement. This is exactly what happened and I want to show you how to have a clean and beautiful solution to this problem.

So my client’s requirement is the following:

Passwords should expire in 45 days.

I’m currently using the default ASP.NET membership provider. It gives you a database schema ready to manage users and roles. You just have to use ASP.NET Configuration Tool to create Roles and Users, decorate your Controllers/Actions with the Authorize attribute and you’re good to go most of the time. The default membership provider allows a fast project start – no doubt – but as always there’s something that must be done according to the infinitude of possible requirements that change project by project. One of these not contemplated things is a setting in the default provider for handling user’s password expiration. We have to roll our own code to manage this.

My friend Google told me that some folks have already done some work related to this and as always I borrow some of their code and adapt it to my specific case/technology.

First I created a PasswordExpiredAttribute that derives from/extends the AuthorizeAttribute. Here’s its code:

public class PasswordExpiredAttribute : AuthorizeAttribute
{
    private static readonly int PasswordExpiresInDays =
int.Parse(ConfigurationManager.AppSettings["PasswordExpiresInDays"]);

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        IPrincipal user = filterContext.HttpContext.User;
           
        if(user != null && user.Identity.IsAuthenticated)
        {
            MembershipUser membershipUser = Membership.GetUser();

            TimeSpan ts = DateTime.Today - membershipUser.LastPasswordChangedDate;

            // If true, that means the user's password expired
            // Let's force him to change his password before using the application
if (ts.TotalDays > PasswordExpiresInDays) { filterContext.HttpContext.Response.Redirect( string.Format("~/{0}/{1}?{2}", MVC.Account.Name, MVC.Account.ActionNames.ChangePassword, "reason=expired")); } } base.OnAuthorization(filterContext); } }

As you see, the code goes inside the OnAuthorization overridable method. I get the PasswordExpiresInDays setting from the Web.config <appSettings> section. This gives an easy way to change the requirement in the future without the need of recompiling the whole app.

<appSettings>
    <add key="PasswordExpiresInDays" value="45" />
</appSettings>

The code explains itself but let’s go through it:

1 - If the User is authenticated, let’s get his membership data;
2 - A TimeSpan is useful for getting the difference in days between Today and the last time the user changed his password ( LastPasswordChangedDate )
3 - Check if the TimeSpan.TotalDays is greater than the PasswordExpiresInDays setting we got from the Web.config file. If true the user must change his password and we redirect him to the ChangePassword view.

Note 1: I’m using T4MVC to retrieve the Controller and Action names in the code above. You should take a look at it! Really…

Note 2: See that "reason=expired" in the response redirect URL? I’m using this querystring as a route parameter inside the ChangePassword action method to display a message to the user informing him that he’s being asked to change the password because it has expired.

/// <summary>
/// This allows the logged on user to change his password.
/// </summary>
public virtual ActionResult ChangePassword(string reason)
{
    var viewModel = new ChangePasswordViewModel();

    if (reason != null)
    {
ShowMessage(Infrastructure.Notification.MessageType.Warning, Localization.PasswordExpired, true); } return View(viewModel); }

By the way: I use MvcNotification infrastructure by Martijn Boland to display beautiful messages to the user.

OK, getting back to the main point… now it’s just a matter of applying the PasswordExpiredAttribute filter to every controller of the app but the AccountController. With ASP.NET MVC 3 it’s easy to apply a filter to every controller and action using GlobalFilters. Instead of going controller by controller to add this attribute we can just register it as a global filter in the Global.asax file:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
filters.Add(new PasswordExpiredAttribute()); }

Doing so the PasswordExpiredAttribute will be executed for every controller and action but there’s a problem with the above approach. Since it’s a global filter, it’ll be executed even for the AccountController. Remember: we don’t want it to be executed for the AccontController… How can we exclude a global filter from a single controller or action? To achieve this, there’s an awesome thing we can do: create a ExcludeFilterAttribute and a ExcludeFilterProvider. WOW, ASP.NET MVC has a Filter Provider that gives us even more power when working with filters. Look here for the complete story: Exclude a Filter by Ori Calvo. I’ve uploaded the source code files here: ExcludeFilterAttribute.cs and ExcludeFilterProvider.cs

Now it’s just a matter of decorating the AccountController with the ExcludeFilter attribute like this:

[ExcludeFilter(typeof(PasswordExpiredAttribute))]
public partial class AccountController : BaseController
{
...
}

The ExcludeFilter attribute explicitly tells the ASP.NET MVC runtime to ignore the PasswordExpiredAttribute for the AccountController.

With this in place, once the logged in user tries to access any part of the site and his password is expired, he'll be redirected to the ChangePassword view and won't be allowed access to anywhere else in the site until he changes the password. This is great and the requirement is implemented.

Of course in software there are multiple ways of doing the same thing. If you know of any better option, please share you knowledge in the comments.

Hope it helps.

Bonus
While working on this requirement I posted a question at StackOverflow regarding the use of Web.config settings as magic strings. I’ve found a nice way to let the code a little bit cleaner. So, if you want a nice way to access your Web.config app settings as properties with compile time checking and nice error handling, you can do as described here: T4MVC for Web.config <appSettings>

This is a much better/cleaner code IMO (see the AppSettings class that was automatically generated with the T4 template):

public class PasswordExpiredAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        IPrincipal user = filterContext.HttpContext.User;

        if (user != null && user.Identity.IsAuthenticated)
        {
            MembershipUser membershipUser = Membership.GetUser();

            TimeSpan ts = DateTime.Today - membershipUser.LastPasswordChangedDate;

            // If true, that means the user's password expired
            // Let's force him to change his password before using the system
            if (ts.TotalDays > int.Parse(AppSettings.PasswordExpiresInDays))
            {
                filterContext.HttpContext.Response.Redirect(
                    string.Format("~/{0}/{1}?{2}", MVC.SGAccount.Name, MVC.SGAccount.ActionNames.ChangePassword,
                    "reason=expired"));

            }
        }

        base.OnAuthorization(filterContext);
    }
}

References:
ASP.NET MVC Authentication - Global Authentication and Allow Anonymous by Jon Galloway

ASP.NET MVC Authentication - Customizing Authentication and Authorization The Right Way by Jon Galloway

Exclude a Filter by Ori Calvo

Introducing System.Web.Providers - ASP.NET Universal Providers for Session, Membership, Roles and User Profile on SQL Compact and SQL Azure by Scott Hanselman

Conditional Filters in ASP.NET MVC 3 by Phil Haack

T4MVC by David Ebbo