IdentityServer4,ASP.NET Identity for Authentication & Authorization with React JS client

IdentityServer4,ASP.NET Identity for Authentication & Authorization with React JS client

Very recently I had a scenario where I have to implement two web apps. for both apps I had to implement authentication and authorization. I spent quite sometime researching on what is the better way to implement this & I hope this saves your time for anyone interested. anyway after doing my research I decided to use the following technologies...

  1. IdentityServer4 :- for authentication & authorization
  2. ASP.NET Identity :- User information storage
  3. .NET API :- API protected by IdentityServer4
  4. React :- React & Typescript Client App that is going to consume API

Lets start coding...

Step 1: Identity Server We can either create empty project and do all the work by our self or we can use the one of the IdentityServer4 templates.to keep things simple I'm going to use one of the templates by running the following commands.

dotnet new -i identityserver4.templates

To see the installed templates run

dotnet new -l

There are couple of template options to choose from.in this case we want to use ASP.NET Identity as the user data storage so we will run this command

dotnet new is4aspid

now that we have our project ready its time to edit some of the code. find Config.cs file which contains the identity configuration. the first this we are going to do is to add the Api resource

public static IEnumerable<ApiResource> ApiResources
=> new ApiResource[]
   { new ApiResource("sample.api","Sample Api") 
     { 
       Scopes = new string[] 
               { ProtectedApiScopes.ScannerApiScope.Name}}
      };

next step is to add our Client SPA app to the clients list

public static IEnumerable<Client> Clients =>
new Client[]
{
   new Client
   {
      ClientId = "sample.spa",
      ClientName = "Sample SPA",
      AllowedGrantTypes = GrantTypes.Implicit,
      AllowAccessTokensViaBrowser = true,
      RequireConsent = false,
      AccessTokenLifetime = 120,
      RedirectUris = { "http://localhost:3000"},
      PostLogoutRedirectUris ={"http://localhost:3000"},
       AllowedCorsOrigins = { "http://localhost:3000" },
       AllowedScopes = {
           IdentityServerConstants.StandardScopes.OpenId,
           IdentityServerConstants.StandardScopes.Profile,
       },
 };

This is enough change for the identity configuration, now we have to add our configuration to IServiceCollection in StartUp.cs as follows

services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddInMemoryPersistedGrants()             
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApiResources())
    .AddInMemoryClients(Config.GetClients())
    .AddAspNetIdentity<ApplicationUser>();

In production its better to store your identity server configuration in the database, but lets keep things simple for now.we are done configuring the identity server, next step is to create & setup the .NET api project.project can be created by running the following command

dotnet new webapi

after creating the project we have to add reference to the IdentityServer4.AccessTokenValidation package.we can then add configuration in the StartUp.cs file by adding the following code

 services.AddAuthentication("Bearer")
         .AddIdentityServerAuthentication(options =>
         {
             options.Authority = "http://localhost:5000";//Identity Server Uri
             options.RequireHttpsMetadata = false;
             options.ApiName = "sample.api";
          });

That's it now we can simply guard any end point by adding [Authorize] attribute on top of it

[Authorize]
[HttpGet]
public IActionResult Get()
{
   return Ok(new { Message= "You are authenticated" })
}

next step is to create & setup our react app.

npx create-react-app sample-spa --template typescript
Or
yarn create react-app my-app --template typescript

After creating the react app. we will add the best oidc library called oidc-react which is the best oidc library I have seen by far & I hope you will come to see why.

npm install oidc-react

create oidc-config.ts file in the src directory of sample-spa project and add the following configuration

export const customOidcConfig: AuthProviderProps = {
  clientId: 'sample.spa',
  automaticSilentRenew: true,
  redirectUri: 'http://localhost:3000/',
  responseType: 'token id_token',
  scope:"openid profile",
  authority: 'http://localhost:5000',
  onBeforeSignIn:()=>{
     /**
      * This method gets executed before signin redirecting begins
      * it can be used to store Uri
      */
  }
  onSignIn:async (user: User | null)=>{
      console.log('PATH',window.location.pathname)
        if(user){
            console.group('[ LOGIN: SUCCESS ]', user);
         };
         window.location.hash = '';
    }
    else{
       console.error('[ LOGIN: ERRNO ]')
    }
  },
  onSignOut:async (options?: AuthProviderSignOutProps) =>{
      console.log('[ SignOutOpts ]', options);
  }
};

next step is to initiate login using the configuration above. find App.tsx file and update it using the following code

<AuthProvider {...customOidcConfig}>
   <AuthCheck/>          
</AuthProvider>

This will automatically initiate the login process. we can also check if the user is logged in by using useAuth hook.

export const AuthCheck: FC =() =>{
   const authState = useAuth();
   return (
     <Fragment>
        { 
          authState && 
          authState.userData && 
          <p>Authenticated</p> 
        }
        {
          (!authState ||
          !authState.userData) && 
          <p>Not Authenticated</p>
        }
     </Fragment>
   )
}

yeah, now we are done. I hope you enjoyed this. Thanks for reading

Happy Coding!!!