Published on

Authentication using OpenId in React/NextJs App With Orchard Core - Part 2

4 min read
Table of Contents

Intro

In this post we will see how to do authentication using OpenId for a React App using Orchard Core.

Prerequisites

To create the CMS and and a sample content follow the Getting started

Create a OpenId Client

Navigate to the https://localhost:5001/Admin and login as a admin.

Navigate to Security -> OpenID Connect -> Scopes -> Add an scope.

Create two scopes role and openid.

Navigate to Security -> OpenID Connect -> Applications -> Add an Application.

Create a client with

Client id : client1

Display Name : client1

Type : Public client

Flows : Allow Authorization Code Flow

Redirect Uris : http://localhost:3000

Consent type: Explicit consent

Allowed scopes: openid, role

Click Save

Update the CORS policy

Create a new policy to and set is as default. All credentials and any origin, headers and methods.

Update the CORS in CMS Startup.cs file under ConfigureServices function.

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(o => o.AddDefaultPolicy(builder =>
    {
        builder
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader();
    }));
    services.AddOrchardCms();
}

Create a NextJS App

yarn create next-app --typescript

Add npm packages

yarn add oidc-client-ts react-oidc-context @apollo/client

Add .env file

NEXT_PUBLIC_BASE_URL=https://localhost:5001
NEXT_PUBLIC_GRAPHQL_URL=https://localhost:5001/api/graphql

Create login component

Inside the components folder create Login.tsx

import React from 'react'
import { useAuth } from 'react-oidc-context';

const Login = () => {
    const auth = useAuth();
    if (auth.isLoading) {
      return <div>Loading...</div>;
    }

    if (auth.error) {
      return <div>Oops... {auth.error.message}</div>;
    }

    if (auth.isAuthenticated) {
      return (
        <div>
          Hello {auth.user?.profile.sub}{" "}
          <button onClick={auth.removeUser}>Log out</button>
        </div>
      );
    }

    return <button onClick={auth.signinRedirect}>Log in</button>;
}

export default Login

Create Languages component

Inside the components folder create Languages.tsx

import React from "react";
import { gql, useQuery } from "@apollo/client";

interface Props {}

const LanguagesQuery = gql`
  query Languages {
    language {
      code
      createdUtc
      displayText
      modifiedUtc
      name
    }
  }
`;

const Languages = (props: Props) => {
  const { loading, error, data } = useQuery(LanguagesQuery);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;

  return data.language.map((lang: any) => (
    <div key={lang.code}>
      <p>{lang.name}</p>
    </div>
  ));
};

export default Languages;

Create apollo client

Create a file apollo-client.ts inside the lib folder.

import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { User } from 'oidc-client-ts'

const httpLink = createHttpLink({
  uri: process.env.NEXT_PUBLIC_GRAPHQL_URL,
})

const authLink = setContext((_, { headers }) => {
  // get the authentication token from local storage if it exists
  const oidcStorage = sessionStorage.getItem(
    `oidc.user:${process.env.NEXT_PUBLIC_BASE_URL}:client1`
  )
  const token = User.fromStorageString(oidcStorage!).access_token
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    },
  }
})
const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
})
export default client

Update _app.tsx

import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { AuthProvider } from "react-oidc-context";
import { ApolloProvider } from "@apollo/client";
import client from '../lib/apollo-client';
function MyApp({ Component, pageProps }: AppProps) {
  const oidcConfig = {
    authority: "https://localhost:5001",
    client_id: "client1",
    redirect_uri: "http://localhost:3000",
    response_type: "code",
    scopes: "openid email"
  };
  const onSignin = () => {
    location.href = "/";
  };
  return (
    <AuthProvider {...oidcConfig} onSigninCallback={onSignin}>
      <ApolloProvider client={client}>
        <Component {...pageProps} />
      </ApolloProvider>
    </AuthProvider>
  );
}

export default MyApp

Update the index page

import type { NextPage } from 'next'
import Login from './../components/Login';
import Languages from './../components/Languages'

const Home: NextPage = () => {
    return (
      <div>
        <Login />
        <Languages></Languages>
      </div>
    );
}

export default Home

Run both CMS and Next app

Navigate to the CMS project and run

dotnet run

Navigate to the NextJS project and run

yarn // To install the npm packages
yarn dev // To run the app

Repo : https://github.com/antosubash/OrchardCoreReactAuth