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...
- Open Visual Studio and select New Project.
- From the New Project dialog box, select .NET Core and then ASP.NET Core Web Application (fig 1)
- From the ASP.NET Core Web Application dialog box, select React.js. (fig 2)
- 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:
- Right-click on the project in the solution, and select Unload Project (fig 3.)
- Then, right-click on the project and select 'Edit BssReactOidcLoginApp.csproj' (fig 4).
- 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.
- Save and close the file. Then right-click on the project and select Reload Project (fig 6).
- 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.
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 from 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).
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.
-
- services.AddMvcCore()
- .AddAuthorization()
- .AddJsonFormatters();
- services.AddAuthentication("Bearer")
- .AddIdentityServerAuthentication(options =>
- {
- options.Authority = "https://ids.bssdev.biz";
- options.RequireHttpsMetadata = true;
- 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();
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").
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.
-
- mport { 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.
-
- 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;
-
- _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.
-
- 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.
-
- .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() {
-
- 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.
-
- 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 username. Click the drop-down and select Clients to create a client app (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).
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).
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 URL 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/.
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.
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.
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).
If you click on the Fetch data' link, you'll notice you see our Unauthorized component.
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 username 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 Datalink, you see the normal page (fig 18). Click the Logout link, then the Fetch Data link and you'll see the Unauthorized message again!
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.
- @Html.Partial("_adReactTS")
Return to Index
- @Html.Partial("CommentBox")