How to Implement OIDC Authentication and Authorization with React without Redux

by

In this tutorial I'll be implementing OpenID Connect (OIDC) Authentication and Authorization in an ASP.Net Core React.js Single Page Application without using Redux (there's absolutely no need for it). OIDC is the latest and greatest way to handle authentication and authorization and features such things as: Single Sign On (SSO), Authentication as a Service, Claims Based Authorization, and more - in a nutshell: it's how you want to be handling authentication and authorization.

I'll be using Visual Studio 2017 Community with ASP.Net Core 2.x, React.js, and EntityFramework Core. This tutorial assumes that you are familiar with Visual Studio and ASP.NET applications and have written some C# and Javascript code, at least at a rudimentary level.

Building our React Application

Visual Studio 2017 includes a basic React project template, so we'll be starting with that. Let's create our project...

  1. Open Visual Studio and select New Project.
  2. From the New Project dialog box, select .NET Core and then ASP.Net Core Web Application (fig 1)
    fig 1
    fig 1
  3. From the ASP.Net Core Web Application dialog box, select React.js. (fig 2)
    fig 2
    fig 2
  4. Name the application 'BssReactOidcLoginApp'.

Configuring the Application

tsconfig.json

Our OIDC component uses the Any Typescript data type so we have to turn off TypeScript's 'strict' mode. In tsconfig.json, which lives in the root of your application, set 'strict' to false as shown below.

"compilerOptions": { "strict": false

A Nuget Package for the 'Heavy Lifting'

We need to install a Nuget Package that handles the 'heavy lifting' of OIDC token validation called IdentityServer4.AccessTokenValidation. To that end run the following command from the Package Manager Console ( Tools | Nuget Package Manager | Package Manager Console):

Install-Package IdentityServer4.AccessTokenValidation -Version 2.6.0

We also need to install a couple of NPM packages: oidc-client and browserify (to allow us to call the oidc-client package from our client app). We also need a script to build our browserify client bundle. To accomplish this simply add the following block to your Package.json file below the "devdependencies{}" block and Visual Studio will automatically download the packages when you save the file.

    "dependencies": {
        "oidc-client": "^1.4.1",
        "browserify": "^16.2.2"
    },
    "scripts": {
        "build-js": "browserify -r oidc-client -o wwwroot/dist/bundle.js"
    }

We can run our NPM script a couple of different ways: 1. There's an excellent free Visual Studio Extension called: NPM Task Runner that you can use. For this tutorial we'll be taking a less glamorous approach: using Visual Studio's Exec command, which doesn't require you to install anything. We'll need to edit our .csproj file. To do so:

  1. Right-click on the project in the solution, and select Unload Project (fig 3.)
    fig 3
    fig 3
  2. Then right-click on the project and select 'Edit BssReactOidcLoginApp.csproj' (fig 4).
    fig 4
    fig 4
  3. Scroll down to the <Target Name="DebugRunWebpack".. > tag, and type Exec command show on line 69 below.
    Note: there are two <Target> tags; make sure to select the one with the name DebugRunWebpack.
    fig 5
    fig 5
  4. Save and close the file. Then right-click on the project and select Reload Project (fig 6).
    fig 6
    fig 6
  5. Rebuild the project. If the build succeeds and you see the browserify script on the last line (fig 7), congratulate yourself - all went well!
    Note: the compile configuration should be set to Debug.
    fig 7
    fig 7

The OidcLogin React Component

We need to install the OidcLogin React component and tweak our app a bit.

The BetterSoftwareSolutions.OidcLogin is a free Visual Studio 2017 Extension. You can download it: here. You'll need to close Visual Studio to install it. When you're ready to continue, just return to this spot.

If you're resuming this tutorial, you should have installed the free BetterSoftwareSolutions.OidcLogin Visual Studio Extension mentioned above. Once you've installed the extension, right-click on the project, then select Add New Item, then select the React OidcLogin item (fig 8).


fig 8
fig 8

If you open the OidcLoginReadme file, you'll see that The OidcLogin item template added the following items to the project. It also tells you what you need to do next (Next Steps).

  • wwwroot\dist\bundle.js
  • ClientApp\components\OidcLogin\*
  • ClientApp\components\OidcConfig.tsx
  • Views\Home\SignedIn.cshtml

Configuring our Application

Let's configure OIDC authentication and authorization in our app...

You'll notice we've already completed some of the Next Steps. We'll continue on at step 3: Change Startup.ConfigureServices(). As the readme indicates we want to copy the code block shown below to the ConfigureServices() method in Startup.cs. Paste it just before the services.AddMvc() line.

    // add this block before services.AddMvc()
    services.AddMvcCore()
    .AddAuthorization()
    .AddJsonFormatters();
    services.AddAuthentication("Bearer")
    .AddIdentityServerAuthentication(options =>
    {
    options.Authority = "https://ids.bssdev.biz";
    options.RequireHttpsMetadata = true;//change this to false for dev/testing only
    options.ApiName = "enter your api name from identity server(ids.bssdev.biz) here";
    });

We also need to add a line to the Configure() method in the same file. Paste it just before app.UserMvc();

    //add this line was added before app.UseMvc();
    app.UseAuthentication();

We need to add the [Authorize] attribute to our SampleDataController (our"API"). This protects our API from un-authenticated access, i.e. all users must login before they can access anything in our API. Paste it just before the class declaration ("public class SampleDataController").

    //this protects the entire controller
    [Authorize]

We need a way for users to login, so we'll add the <OidcLogin / > component to the navmenu.tsx component (ClientApp/components). Paste the following import statement at the top, below the final import statement.

    //add an import statement to the top of the component
    import { OidcLogin } from './OidcLogin/OidcLogin'

Then add the <OidcLogin / > component to the render() method just below the closing </div > of the <div className='navbar-header' >. Only add the <oidclogin /> line.

//add only the <OidcLogin / > line
public render() {
    return <div classname='main-nav' >
        <div classname='navbar navbar-inverse' >
            <div classname='navbar-header' >
            ...
            </div >
            <oidclogin />
            ...
        </div >
}

We also need to modify our component views slightly. We'll be making changes to ClientApp/components/FetchData.tsx. Start by adding the following imports to the top of the components.

    import * as Oidc from 'oidc-client'
    import { OidcUnauthorized } from './OidcLogin/OidcUnauthroized'
    import * as Config from './OidcConfig'

Next we need to add a field to our component state to keep track of the authenticated user. Paste the _user?: any; line just before the closing } of the FetchDataExampleState interface.

interface FetchDataExampleState {
    forecasts: WeatherForecast[];
    loading: boolean;
    //here's where we put our authenticated user
    _user?: any;
}

In the constructor just below the line where the state is initialized, paste the following block that calls UserManager and retrieves the logged in user.

    //get the authenticated user
    let mgr = new Oidc.UserManager(Config.UserManagerSettings);
    mgr.getUser().then((user) => {
        this.setState({ _user: user });
        console.log(user);
    })

There's one other thing we need to do to our component and that's modify each of our fetch() calls. We'll need to comment the existing fetch() call and replace it with the block shown below. You'll notice that our new fetch() call includes Config.FetchSettings(this.state._user). , which contains settings required for OIDC and comes from ClientApp/components/OidcConfig.tsx.

         //replace the existing fetch() statement with this
        .then(() => {
            console.log(this.state);
            return fetch('api/SampleData/WeatherForecasts', Config.FetchSettings(this.state._user))
        })

The last thing we need to do to our component, is add a couple of lines to our render() method to check for an authenticated user, i.e. if we have no logged in user we return <OidcUnauthorized />, which simply displays an 'Unauthorized' message.

   	public render() {
        //add this to the top of the render() method
        if (this.state._user == null)
            return 

Redirecting the User after Login

We need to redirect our user after he/she logs in. To handle that we have an MVC view called SignedIn, which was added by the OidcLogin item template. All we need to do is modify our HomeController to serve the view when it's requested. Add the following block to HomeController.

    	// This method is used to redirect a user after they log in.
        public IActionResult SignedIn()
        {
            return View();
        }

And that takes care of our application. In the next section we'll create a Client App in our Identity Server and then run the app.

Creating the Identity Server Client App

We'll be using BSS Identity Server at ids.bssdev.biz . You'll need to register and create a free developer account. When you're ready to continue, we'll be right here.

Once you've created your free developer account and logged in, you'll see a drop-down menu in the upper right next to your user name. Click the drop-down and select Clients to create a client app (fig 9).


fig 9
fig 9

The easiest way to create a client app is to use the wizard. From the Client Apps page, we'll click the 'Create Client App Wizard' link in the upper left to get started, then select the Single Page Application button (fig 10).


fig 10
fig 10

On the first wizard page you'll want to specify a Client Id and Client Name. I like to use the exact name of my application for the Client Id, that way there's never any doubt which app it's for. Simply copy the namespace from the HomeController and paste it in the Client Id field. Then again into the Client Name field, which is displayed to the user. You can add spaces to make it more legible if you like, then click the Next button (fig 11).


fig 11
fig 11

You'll need your site url for the next page. Right-click your project and select Properties, then select the Debug tab from the left side of the properties window. Copy the App Url and paste it into the Redirect Uri field, then add 'Home/SignedIn' to the end. This is the view that will handle the redirect after the user logs in. Paste your App Url again into the Post Logout Redirect Uri and CORS Origin fields. After a user logs out, we'll simply redirect them to the root page, which does not require them to be logged in. CORS (Cross Origin Resource Sharing) Origin simply indicated the site root, which should never have a trailing /.


fig 12
fig 12

Step 4 is where we specify the scopes that the client app needs access to. Openid and profile are required for OIDC authentication and for displaying the username respectively. We also need to allow the client access to the integrated "api" (our FetchDataController). Ordinarily we also need to create an API and Scope in the Identity Server, but if we check the 'Create an API & Scope using the same name as the Client App' box, Identity Server will create those automatically for us. Check that box and then click the Next button.


fig 13
fig 13

And that does it. Simply click the Finish button on the final wizard page and we're done.

Wrapping Things Up

We've added OIDC authentication and authorization to our React Single Page Application without needing Redux, and created our Client App and Api Scope in our identity server. We just need to check some settings and we can run our app.

Open startup.cs, scroll down to the ConfigureServices() method, and make sure the ApiName is exactly as it appears in Identity Server. It should be the same as the Client.Client Id since we checked the box to have Identity Server automatically create our API. If you like you can select APIs from the Admin menu and check the Name field on the APIs page.

Lastly, let's check the settings in our OidcConfig.tsx file (found in ClientApp/components). You'll want to make sure that the client_id, redirect_uri, the last part of the scope, and the post_logout_redirect_uri are correct (fig 14). Note: The client_id and the last part of the scope should be the same since we had Identity Server create a scope for us with the same name.


fig 14
fig 14

Let's Run our App

We can now run our app. It's a good idea to Rebuild to make sure everything is still good, Then Run without debugging.

You'll notice we now have a Login link in the top right corner of the navigation pane (fig 15).


fig 15
fig 15

If you click on the Fetch data' link, you'll notice you see our Unauthorized component.


fig 16
fig 16

If you click on the Login link one of two things may happen: 1. if you are already logged into Identity Server, you will simply see your user name and the Login link will be replaced with a Logout link; 2. if you are not already logged in, you are redirected to the Identity Server Login page (fig 17). Then after successfully logging in, you are redirected back to your site. You'll then notice it now shows your UserName along with a Logout link. And if you click on the Fetch Data link, you see the normal page (fig 18). Click the Logout link, then the Fetch Data link and you'll see the Unauthorized message again!


fig 17
fig 17

fig 18
fig 18

Final Words

Just one final comment...the OidcLogin Visual Studio Extension also includes a new Project Template, which creates a React Single Page App with Oidc authentication / authorization built it. We could have created our app by simply selecting it, but that wouldn't have been as much fun..or educational!

Best of luck in your React development. We offer several tools that simplify React development we hope you will look at (bssdev.biz).


ReactTS/ReactTurbo-splash128.png ReactTS/ReactTurbo-splash128.png

React Turbo Scaffolding

Scaffold your entire ASP.NET Core application with a single button click.

  • 100% React Component architechture
  • Full CRUD support
  • Built-in validation
  • Built-in sorting and paging and much, much more!

Learn more »





Return to Index

Comment Form is loading comments...