Auth Components

ESW-TS provides React components that integrate with the CSW Authentication and Authorization Service. UI applications can use these React components to enable the application to show or hide components based on the authentication and authorization policy.

Application Configuration

Web application needs following configurations in order to get access token from keycloak server. This application specific config object should be passed in AuthContextProvider component. There are two configurations needed for a web application i.e. realm, clientId

realm is a mandatory configuration which specifies in keycloak server under which client your application is registered.

clientId is a mandatory configuration which specifies the client id of the app as per registration in keycloak server.

Typescript
export const AppConfig = {
  realm: 'TMT',
  clientId: 'tmt-frontend-app',
  applicationName: 'test-app'
}

Components

ESW-TS exposes the following React components.

Type definition for all components used by services can be found here

Components can be imported as shown in code snippet below

Typescript
import { AuthContext, Logout, Login } from '@tmtsoftware/esw-ts'

AuthContextProvider

AuthContextProvider is wrapper over a React Context.Provider. A JSON configuration must be passed in that contains the application specific AAS server configuration (e.g. clientId, realm). When a user logs in, an AAS Server is instantiated, with the UI application specific configuration overriding the predefined configuration. Once the AAS sever is instantiated, an auth object is created with the needed attributes and APIs. This auth object is available to other React components; since AuthContextProvider is a Provider, its data can be shared with any of the children React components in its tree in a Consumer component (see below). All Consumers that are descendants of a Provider will re-render whenever the AuthContextProvider’s state changes, e.g. a user authorizes. It is recommended to use AuthContextProvider to wrap the entire application so that data can be shared anywhere in application via a Consumer.

Typescript
<AuthContextProvider config={AppConfig}>
  <BrowserRouter>
    <div>
      <NavComponent />
      <Route
        exact
        path='/secured'
        render={(_) => (
          <CheckLogin error={<LoginError />}>
            <Write />
          </CheckLogin>
        )}
      />
      <Route exact path='/config' render={(_) => <ConfigApp />} />
      <Route
        exact
        path='/example_admin'
        render={(_) => (
          <CheckLogin error={<LoginError />}>
            <RealmRole
              realmRole='example-admin-role'
              error={
                <RoleError
                  message={'User do not have role : example-admin-role'}
                />
              }>
              <div>Example admin role specific functionality</div>
            </RealmRole>
          </CheckLogin>
        )}
      />
      <Route
        exact
        path='/example_user'
        render={(_) => (
          <CheckLogin error={<LoginError />}>
            <RealmRole
              realmRole='person-role'
              error={
                <RoleError
                  message={'User do not have role : person-role'}
                />
              }>
              <div>Person role specific functionality</div>
            </RealmRole>
          </CheckLogin>
        )}
      />
      <Route exact path='/public' component={Read} />
    </div>
  </BrowserRouter>
</AuthContextProvider>

Consumer

Consumer is similar to a React Context.Consumer. The shared auth object from the AuthContextProvider can be accessed using a Consumer component.

Typescript
const { auth } = useContext(AuthContext)
return (
  <div className='nav-wrapper'>
    {auth && auth.isAuthenticated() ? (
      <div>
        Hello, you are logged in
        <div>Open functionality</div>
      </div>
    ) : (
      <div>
        Hello, you are not logged in
        <div>Open functionality</div>
      </div>
    )}
  </div>
)

Login

The Login component instantiates an AAS server with the configurations provided. It redirects to an AAS server login page for the user to login. After login, the auth object in the context is updated with the appropriate values, e.g. token, realm etc.

Typescript
<Login />

Logout

The Logout component logs out the user from the AAS server. It clears the auth object stored in the context.

Typescript
  <Logout />
) : (

CheckLogin

CheckLogin component provides ability to show something only if the user is logged in. In the following code snippet, Write is a React component that is shown only if the user is logged in. The behavior if the user is not logged in can be defined by an HTML element or React component that is passed into the component as an error property, shown as an ExampleError Component in following snippet.

Typescript
<CheckLogin error={<LoginError />}>
  <Write />
</CheckLogin>

RealmRole

RealmRole component provides the ability to show something only if the user is logged in and has the specified realm role. In the following code snippet, the contents of the div block are shown only if the user is logged in and has the realm role specified in the realmRole prop. Similar to CheckLogin, the behaviour if the user is not logged in can be optionally defined by an HTML element or React component that is passed into the component as an error property, shown as an ExampleError Component in following snippet.

Typescript
<RealmRole
  realmRole='example-admin-role'
  error={
    <RoleError
      message={'User do not have role : example-admin-role'}
    />
  }>
  <div>Example admin role specific functionality</div>
</RealmRole>

Example

Here is an example of a Config App(React App) which uses ConfigService client and Auth components in order to authorize ConfigService

Typescript
const ExampleApp = () => {
  return (
    <div className='row card col s12 m7'>
      {
        <AuthContextProvider config={AppConfig}>
          <BrowserRouter>
            <div>
              <NavComponent />
              <Route
                exact
                path='/secured'
                render={(_) => (
                  <CheckLogin error={<LoginError />}>
                    <Write />
                  </CheckLogin>
                )}
              />
              <Route exact path='/config' render={(_) => <ConfigApp />} />
              <Route
                exact
                path='/example_admin'
                render={(_) => (
                  <CheckLogin error={<LoginError />}>
                    <RealmRole
                      realmRole='example-admin-role'
                      error={
                        <RoleError
                          message={'User do not have role : example-admin-role'}
                        />
                      }>
                      <div>Example admin role specific functionality</div>
                    </RealmRole>
                  </CheckLogin>
                )}
              />
              <Route
                exact
                path='/example_user'
                render={(_) => (
                  <CheckLogin error={<LoginError />}>
                    <RealmRole
                      realmRole='person-role'
                      error={
                        <RoleError
                          message={'User do not have role : person-role'}
                        />
                      }>
                      <div>Person role specific functionality</div>
                    </RealmRole>
                  </CheckLogin>
                )}
              />
              <Route exact path='/public' component={Read} />
            </div>
          </BrowserRouter>
        </AuthContextProvider>
      }
    </div>
  )
}

ConfigApp component which uses Config Service APIs:

Typescript
const ConfigApp = () => {
  return (
    <div className='row card col s12 m7'>
      <ConfigServiceProvider authContext={AuthContext}>
        <ListConfig />
        <GetConfig />
        {
          <CheckLogin error={<LoginError />}>
            {/*// #create-config-component*/}
            <RealmRole
              realmRole='config-admin'
              error={
                <RoleError
                  message={
                    "User do not have role 'config-admin' to create config"
                  }
                />
              }>
              <CreateConfig />
            </RealmRole>
            {/*// #create-config-component*/}
          </CheckLogin>
        }
      </ConfigServiceProvider>
    </div>
  )
}

ConfigServiceProvider is the component where instance of Config Service gets created:

Typescript
export interface ConfigServiceProps {
  authContext: typeof AuthContext
  children: React.ReactNode
}

const ConfigServiceProvider = (props: ConfigServiceProps) => {
  const { authContext, children } = props

  const [configService, setConfigService] = useState<ConfigService>(
    defaultConfigServiceState
  )
  const { auth } = useContext(authContext)

  const resetConfigService = async () => {
    //Authenticating config service
    const service = await ConfigService(auth ? auth.token : () => '')
    setConfigService(service)
  }

  useEffect(() => {
    resetConfigService().catch(() =>
      window.alert('config server is not available')
    )
  }, [auth])

  return (
    <ConfigContext.Provider value={configService}>
      {children}
    </ConfigContext.Provider>
  )
}

Source code for the full example can be found here

Technical Description

See Auth Components Technical Description.