OIDC Authentication with React & Identity Server 4
Tags: react, security, csharp, javascript
Published: 2020-07-26 16:37:34

I put this small demo together with the following objectives:
- Authenticate a React app user via Identity Server 4 using OIDC.
- Store authenticated user details in a central store client side.
- Have a public and a protected route within the app. Only authenticated users can access protected route.
- Fetch data from a protected web API using a JWT. Only authenticated users can access the API.

# Basic Architecture
- React app will serve as the customer facing site.
- Identity Server 4 will implement OpenID Connect and be used to authenticate users.
- .NET Core API will have a protected enpoint that will serve some doughnut-y goodness 🍩.

# Identity Server 🤖
Starting with one of the .NET templates provided by Identity Server, we need to configure our client, API resource and test user. For the purpose of this demo I will just create a single client, API resource and test user: Peter Parker 🕷️.

The `GetClients` function of `config.cs` is configured as follows:
```csharp
public static IEnumerable<Client> GetClients()
{
    return new[]
    {
        new Client
        {
            // unique ID for this client
            ClientId = "wewantdoughnuts", 
            // human-friendly name displayed in IS
            ClientName = "We Want Doughnuts", 
            // URL of client
            ClientUri = "http://localhost:3000", 
            // how client will interact with our identity server (Implicit is basic flow for web apps)
            AllowedGrantTypes = GrantTypes.Implicit, 
            // don't require client to send secret to token endpoint
            RequireClientSecret = false, 
            RedirectUris =
            {             
                // can redirect here after login                     
                "http://localhost:3000/signin-oidc",            
            },
            // can redirect here after logout
            PostLogoutRedirectUris = { "http://localhost:3000/signout-oidc" }, 
            // builds CORS policy for javascript clients
            AllowedCorsOrigins = { "http://localhost:3000" }, 
            // what resources this client can access
            AllowedScopes = { "openid", "profile", "doughnutapi" }, 
            // client is allowed to receive tokens via browser
            AllowAccessTokensViaBrowser = true 
        }
    };
}
```

Also in `config.cs`, we can add our web API as a resource in `GetApis`:
```csharp
public static IEnumerable<ApiResource> GetApis()
{
    return new ApiResource[]
    {
        // name and human-friendly name of our API
        new ApiResource("doughnutapi", "Doughnut API") 
    };
}
```

# Web API 🕸️
Our web API will serve up doughnut freshness from behind a protected endpoint. When calling the API from our React app, we will pass a bearer token in the request headers. The API can verify the token and give us what we want.

In the .NET Core Web API template project we can add bearer token authentication by adding the following to the `ConfigureServices` method in `Startup.cs`:
```csharp
services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        // URL of our identity server
        options.Authority = "https://localhost:5001";
        // HTTPS required for the authority (defaults to true but disabled for development).
        options.RequireHttpsMetadata = false; 
        // the name of this API - note: matches the API resource name configured above
        options.Audience = "doughnutapi"; 
    });
```

Next, we can add the middleware to the app by adding `app.UseAuthentication()` to the `Configure` method of `Startup.cs`. This allows authentication to be performed on every request. 

```csharp
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseCors(builder =>
          builder
            .WithOrigins("http://localhost:3000")
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials()
        );

    app.UseAuthentication();
    app.UseMvc();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Doughnut API is running!");
    });
}
```

# React SPA 👾
We can use Create React App to spin up a quick React project. From there, we can add our components and services for authenticating a user.

### userService.js
We are using IdentityModel's `oidc-client` to implement our OIDC flow in React. I've created a `userService` that will abstract all functionality relating to OIDC and user management. `oidc-client` exposes a `UserManager` class that requires a config object: 

```js
const config = {
  // the URL of our identity server
  authority: "https://localhost:5001", 
  // this ID maps to the client ID in the identity client configuration
  client_id: "wewantdoughnuts", 
  // URL to redirect to after login
  redirect_uri: "http://localhost:3000/signin-oidc", 
  response_type: "id_token token",
  // the scopes or resources we would like access to
  scope: "openid profile doughnutapi", 
  // URL to redirect to after logout
  post_logout_redirect_uri: "http://localhost:3000/signout-oidc", 
};

// initialise!
const userManager = new UserManager(config)
``` 

`userService.js` exports various functions that use the `userManager` class created above.

### Initiating OIDC flow
Using `userService.signinRedirect()`, we can initiate the OIDC login flow. This will redirect the user to the login screen of Identity Server, and once authenticated, will redirect them back to the `redirect_uri` provided when configuring the `UserManager` class.

### Callback Routes
For the simplicity of this demo, 2 callback routes have been configured: `/signin-oidc` and `/signout-oidc`. 

Once the user has logged in, they are redirected to `/signin-oidc` on the client. On page load, `userService.signinRedirectCallback()` will process the response from the OIDC authentication process. Once complete, the user is redirected to the home page and authentication has been successful! Yay!

```js
function SigninOidc() {
  const history = useHistory()
  useEffect(() => {
    async function signinAsync() {
      await signinRedirectCallback()
      // redirect user to home page
      history.push('/')
    }
    signinAsync()
  }, [history])
```

Similarly, when the user logs out they are redirected to Identity Server to confirm logout then back to `/signout-oidc` on the client. This is where we can do any further actions such as redirecting the user to a 'Logout Successful!' page.

### AuthProvider
Inspired by this [Medium article](https://medium.com/@franciscopa91/how-to-implement-oidc-authentication-with-react-context-api-and-react-router-205e13f2d49) on implementing OIDC in React, I used React Context to create an `AuthProvider` to wrap all components of the app. I'm only using this to handle events triggered in our `userManager` class:
```js
userManager.current.events.addUserLoaded(onUserLoaded)
userManager.current.events.addUserUnloaded(onUserUnloaded)
userManager.current.events.addAccessTokenExpiring(onAccessTokenExpiring)
userManager.current.events.addAccessTokenExpired(onAccessTokenExpired)
userManager.current.events.addUserSignedOut(onUserSignedOut)
```

The `UserLoaded` event is used to store the user object from Identity Server in Redux. This user object includes an access token which is added to the authorization header in axios. 

# We're done! 👊
A user of our React app can successfully authenticate via Identity Server and call our web API to get some doughnut-y goodness!

![React app home page](https://dev-to-uploads.s3.amazonaws.com/i/ad2c55gld7quk155umzm.png)

This doesn't implement more advanced features such as silently renewing tokens but it does serve as a demonstration of adding OIDC to a React app.

Feel free to clone the [Github repo](https://github.com/tappyy/react-IS4-auth-demo) and have a poke around the demo and source code. PRs also welcome!

### Further Reading 📖
Some useful links that are related to this post 😃

- [Identity Server Documentation](http://docs.identityserver.io/en/latest/index.html)
- [oidc-client-js Github](https://github.com/IdentityModel/oidc-client-js)