Login
Login components include React components, context and hooks for handling user authorisation, api tokens, session polling and GraphQL queries.
Usage
Table of contents
- Components
- Oidc client hooks
- Api tokens client hooks
- Session poller hooks
- GraphQL module hooks
- Generic signal hooks
- Modules
- Silent renewal
- Signals
- Using without React
- Packages
Components
HDS login components cover most use cases and also handle errors when logging in or getting tokens.
Detailed information about all properties of each component is listed on the API page.
Important
All components and hooks require a LoginProvider to provide context for them.
LoginButton
This component handles the redirection to the OIDC server and also errors if the server rejects the request for OpenID configuration.
Button text is passed and rendered as a child.
<LoginButton errorText="Login failed. Try again!" loggingInText="Logging in">Log in</LoginButton>
All component properties are listed on the API page.
LoginCallbackHandler
This component handles the response when the OIDC server redirects the browser back to the given callback route. When the response is parsed, the browser is usually redirected to another path.
const onSuccess = (user: User) => {// Handle successful login// Usually redirect to a page};const onError = (error: OidcClientError) => {//Handle error};return (<LoginProvider {...providerProperties}><LoginCallbackHandler onSuccess={onSuccess} onError={onError}><div>Logging in...</div></LoginCallbackHandler></LoginProvider>);
All component properties are listed on the API page. See also a note about isHandlingLoginCallbackError(error)
.
LoginProvider
This component creates a React context and initialises all modules. All components and hooks are required to render inside the LoginProvider or React Context will throw an error. Position the context in the component tree like any other React context.
The value of context does not change every time one of its modules changes. Therefore, it does not cause re-renders, so it can be at the top level. For example, if user data or API tokens are renewed, the value of the context is not updated. Only its modules. This way, the application using the LoginProvider is not constantly re-rendered.
Up-to-date user data or API tokens can be accessed from the modules with hooks.
The useSignalListener can be used to update a component when a module of the LoginProvider changes.
const providerProperties: LoginProviderProps = {userManagerSettings: {authority: 'https://tunnistamo.dev.hel.ninja/',client_id: 'exampleapp-ui-dev',scope: 'openid profile email',redirect_uri: 'https://service.fi/callback',},apiTokensClientSettings: { url: 'https://tunnistamo.dev.hel.ninja/api-tokens/' },sessionPollerSettings: { pollIntervalInMs: 300000 },};const MyApp = () => {const { isAuthenticated, getUser, logout } = useOidcClient();const { getApiTokens } = useApiTokens();const { addListener } = useSignalListener();if (isAuthenticated()) {return (<><p>Hello, {user.name}!</p><Button onClick={() => logout()}>Log out</Button></>);} else {return (<><p>You are not logged in.</p><LoginButton errorText="Login failed. Try again!">Log in</LoginButton></>);}};<LoginProvider {...providerProperties}><MyApp /></LoginProvider>;
All component properties are listed on the API page.
SessionEndedHandler
This component listens to the session-ended signal. It is emitted from the Session poller, if it receives a response indicating the user's session has ended. Then the component shows an HDS Dialog and forces the user to log out. The Dialog can be seen in the Storybook. This component can be placed anywhere inside the LoginProvider.
<SessionEndedHandlercontent={{title: 'Session has ended!',text: 'Your session on the server has ended. You will be logged out in this window too.',buttonText: 'Logout',closeButtonLabelText: 'Logout',}}/>
All component properties are listed on the API page.
WithAuthentication
Component for rendering components conditionally, depending on whether the user is authenticated or not.
const HelloUser = ({ user }: { user: User }) => {return <p>Hello, {user.name}!</p>;};const LoginComponent = () => {return <LoginButton errorText="Login failed. Try again!">Log in</LoginButton>;};<WithAuthentication AuthorisedComponent={HelloUser} UnauthorisedComponent={LoginComponent} />;
All component properties are listed on the API page.
WithAuthenticatedUser
Renders its children only if the user is authenticated. Uses WithAuthentication, but allows multiple children.
<WithAuthenticatedUser><p>This is rendered, if the user is authenticated</p></WithAuthenticatedUser>
All component properties are listed on the API page.
WithoutAuthenticatedUser
Renders its children only if the user is not authenticated. Uses WithAuthentication, but allows multiple children.
<WithoutAuthenticatedUser><p>This is rendered, if the user is not authenticated</p></WithoutAuthenticatedUser>
All component properties are listed on the API page.
Oidc client hooks
More detailed information about Oidc client hooks is listed on the API page.
useAuthenticatedUser
Returns a user object if the object is valid, authenticated and passes the isValidUser() check.
useCachedAmr
Returns the user's amr value. It is cached because in some cases it must be decrypted from an id_token
.
useOidcClient
Returns the Oidc client module.
useOidcClientTracking
Returns an array of [signal, reset function, oidcClient instance]
. The hook re-renders the component each time the client emits a signal.
import { useOidcClient, useOidcClientTracking, useAuthenticatedUser, useCachedAmr } from 'hds-react';const OidcClientHooks = () => {const client = useOidcClient();const [lastSignal, reset, oidcClient] = useOidcClientTracking();const user = useAuthenticatedUser();const amr = useCachedAmr();if (user) {return <p>Your amr is: {amr ? amr[0] : 'none'}</p>;} else {return (<ButtononClick={() => {client.login();}}>Log in!</Button>);}};
Api tokens client hooks
More detailed information about Api tokens client hooks is listed on the API page.
useApiTokens
Returns functions for checking tokens and the status of renewal.
useApiTokensClient
Returns the Api tokens client module.
useApiTokensClientTracking
Returns an array of [signal, reset function, apiTokensClient instance]
. The hook re-renders the component each time the client emits a signal.
import { useApiTokensClient, useApiTokens, useApiTokensClientTracking } from 'hds-react';const ApiTokensClientHooks = () => {const client = useApiTokensClient();const { getStoredApiTokens, isRenewing } = useApiTokens();const [lastSignal, reset, apiTokensClient] = useApiTokensClientTracking();if (isRenewing()) {return <p>Your API tokens are being renewed!</p>;}if (getStoredApiTokens()) {return <p>You have API tokens!</p>;}};
Session poller hooks
More detailed information about Session poller hooks is listed on the API page.
useSessionPoller
Returns the Session poller module.
useSessionPollerTracking
Returns an array of [signal, reset function, sessionPoller instance]
. The hook re-renders the component each time the poller emits a signal.
import { useSessionPoller, useSessionPollerTracking } from 'hds-react';const SessionPollerHooks = () => {const poller = useSessionPoller();const [lastSignal, reset, sessionPoller] = useSessionPollerTracking();if (isSessionPollerStoppedSignal(lastSignal)) {<ButtononClick={() => {poller.start();}}>Start polling!</Button>;}};
GraphQL module hooks
More detailed information about GraphQL module hooks are listed on the API page.
useGraphQLModule
Returns the GraphQL module.
useGraphQLModuleTracking
Returns an array of [signal, reset function, GraphQL module instance]
. The hook re-renders the component each time the module emits a signal.
useGraphQL
This function mimics the useLazyQuery hook of the Apollo GraphQL library.
It returns an array of [query, {data, error, loading, refetch}]
. The component using this hook is re-rendered each time the module changes and emits a signal. The returned array updates accordingly.
import { useGraphQL, Button, LoadingSpinner, Notification } from 'hds-react';const GraphQLModuleHooks = () => {const [query, { data, error, loading, refetch }] = useGraphQL();if (loading) {return <LoadingSpinner loadingText="loading" />;}if (error) {return (<div><Notification type="error">An error occured.</Notification><ButtononClick={() => {refetch();}}>Try again.</Button>;</div>);}if (data) {// ...render data}<ButtononClick={() => {query();}}>Load data</Button>;};
Generic signal hooks
Hooks are the easiest way to listen to signals. Most hooks return the last triggered signal and re-render the component. Re-rendering is done only if the listener returns true. So listeners added by hooks must return a boolean.
Hooks dispose of the listeners when the component unmounts. Hooks cannot emit signals.
Detailed information about these hooks is listed on the API page.
useSignalListener
Adds a single listener for all signals. The hook re-renders the component each time a signal is emitted if the listener function returns true.
useSignalTrackingWithCallback
Creates a trigger from given props and calls the callback function if any emitted signals match the trigger.
useSignalTrackingWithReturnValue
Creates a trigger from given props and re-renders the component if any emitted signals match the trigger.
Important! The listener passed to useSignalListener must be memoized or the hook will attach a new listener on each render because the props changed. The old one is disposed of but to avoid unnecessary listeners, use memoization with useMemo
or useCallback
. Note that all triggerFor...
functions are constants and do not need memoization.
useSignalTrackingWithReturnValue and useSignalListener store their arguments in React refs, so memoization is not needed.
The array returned by the useSignalListener and the useSignalTrackingWithReturnValue have the last signal, if any, and a reset function that clears the last signal. This can be used for tracking certain errors and then resetting the array when the user has seen a notification about the error.
import {useSignalListener,useSignalTrackingWithReturnValue,triggerForAllSessionPollerErrors,isSessionEndedSignal,} from 'hds-react';const ListenerComponent = () => {const [ showDialog, setShowDialog ] = useState(false);// Listener function must be memoized when useSignalListener is used!const myListener = useCallback(( signal )=>{if( isSessionEndedSignal(signal) ){setShowDialog(true)}},[]);useSignalListener(myListener);if ( showDialog ) {return <Dialog ... />}};const ListenerComponentWithReturnValue = () => {const [showDialog, setShowDialog] = useState(false);const [sessionEndedSignal] = useSignalTrackingWithReturnValue(triggerForAllSessionPollerErrors);if(isSessionEndedSignal(sessionEndedSignal)){setShowDialog(true)}if (showDialog) {return <Dialog ... />}};
The properties and methods of all hooks are listed on the API page.
Modules
Modules are automatically created by the LoginProvider. Module settings are included in the LoginProvider settings and passed to each module.
Properties, methods, errors and signals of all modules are listed on the API page.
Oidc client
The Oidc client settings are defined in the userManagerSettings property, which is passed to the LoginProvider.
The module is exposed with the useOidcClient hook.
import { useOidcClient } from 'hds-react';const LogoutComponent = () => {const { logout } = useOidcClient();return <Button onClick={() => logout()}>Log out</Button>;};
Requirements
Usage of the Oidc client requires
- An OIDC provider.
- At least
client id
,authority
,scope
and registered callback URLs at the OIDC provider. - Specific
silent_renew.html
for silent token renewal. This is a poorly documented feature in theoidc-client-ts
. This file has a javascript snippet that communicates with the oidc provider "silently" in a hidden iframe. More about token renewal. An example can be found in the silent renewal section.
API
All Oidc client settings, properties, methods and signals are detailed on the API page:
- Module settings.
- Default UserManager settings.
- Methods.
- State change signals.
- Event signals.
- Error signal types.
- Dedicated signal triggers.
- Getting signal payloads.
- Utility functions.
Hooks
Detailed information about these hooks is listed on the API page.
Api tokens client
The Api tokens client
listens to the Oidc client signals and waits until the user is authenticated and then fetches the API tokens. It retries if fetch fails and terminates when max retries are reached. When the user is expiring and renews, API tokens are also renewed automatically.
Important! The Helsinki Profile OIDC provider uses a different kind of API token requests. This module does not support that, but a new module will be added later.
Requirements
URL where to get the tokens from. A user's access token is also required, so the user must be authenticated to fetch API tokens.
API
All Api tokens client
settings, properties, methods and signals are detailed on the API page:
- Module settings.
- Methods.
- Utility functions.
- Event signals.
- Error signal types.
- Dedicated signal triggers.
- Getting signal payloads.
Hooks
Detailed information about these hooks is listed on the API page.
Session poller
The user's session could end outside of the current browser window. The Session poller
calls the userinfo endpoint and notifies when it receives an unauthorized or forbidden response. Successes and other errors are ignored.
Requirements
Polling requires a user and the userManager from the Oidc client. The polling endpoint is fetched from userManager's metadata.
API
All Session poller
settings, properties, methods and signals are detailed on the API page:
- Module settings.
- Methods.
- Utility functions.
- Event signals.
- Error signal types.
- Dedicated signal triggers.
- Getting signal payloads.
Hooks
Detailed information about these hooks is listed on the API page.
GraphQL module
Loading data from a graphQL server may require user authentication and api tokens. The GraphQL module can be set to listen to the Api token client and start querying when api tokens are ready. It can pick the correct api token and set it to the query headers.
The module can also be used without api tokens and can be controlled manually.
Requirements
To execute a query, the following requirements must be met:
- A graphQLClient (ApolloClient) must be configured and passed to the
GraphQL module
. - A query document must be provided.
Detailed information about authenticated queries is on the API page.
API
All GraphQL module
settings, properties, methods and signals are detailed on the API page:
- Module settings.
- Methods.
- Utility functions.
- Event signals.
- Error signal types.
- Dedicated signal triggers.
- Getting signal payloads.
Hooks
Detailed information about these hooks is listed on the API page.
Silent renewal
The oidc-client-ts uses silent_renew.html
for user token renewal. Save this file to your server and place the URL in the settings as silent_redirect_uri
.
<!DOCTYPE html><html lang="en"><head><title>Silent renewal</title></head><body><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/oidc-client-ts/2.2.2/browser/oidc-client-ts.min.js"integrity="sha512-pt8b5O4w5Y9/xZpIhPN8Soo/YbC95SxHn0P/Mu39iYB2Ih/09TMS3Id5XPqve2f8DPC6voXOzgQNojCuqO6A4w=="crossorigin="anonymous"referrerpolicy="no-referrer"></script><script>var mgr = new oidc.UserManager({});mgr.signinSilentCallback().catch(error => {console.error('silent_renew.html error', error);});</script></body></html>
Signals
Signals can be listened to with hooks. Each module has its own hook for listening to signals only from that module. There are also hooks for listening to all signals and filtering is possible with triggers.
Detailed information is in the API section.
Listening to signals
A signal listener is a function that receives one argument: the signal. A listener can listen to all signals or just one type or signal with a certain namespace. Listeners can be even more specific and listen to signals with certain payloads. In short, a listener can listen to any properties of the signal and is triggered when all properties match.
The listener is called only if the emitted signal matches the given props.
For example, if the trigger props (the first argument) passed to beacon.addListener(trigger, listener)
is { type:'error' }
, the listener (second argument) is called when the emitted signal has a matching type. It does not matter what other props the signal has.
If the trigger props are { namespace:'myModule', payload:{type:'click'} }
, the emitted signal must have those properties with the same, exact values. Other properties are not checked.
The trigger can also be a function. Internally all triggers are converted to functions.
const listener = (signal) => {// Do something with the signal.};// Listen to all error signalsconst trigger = { type: 'error' };useSignalTrackingWithCallback(trigger, listener);
Getting signal payloads
Sometimes the most significant part of a signal is the payload. There are predefined payload getters for the Oidc client. If the given signal is not from the Oidc client
or is not of the given type, the function returns null. So there is no need to pre-check the signal namespace or type.
import {useSignalTrackingWithReturnValue,triggerForAllOidcClientSignals,getOidcClientStateChangePayload,oidcClientStates,} from 'hds-react';const StateChangeIndicator = () => {const [lastSignal, reset] = useSignalTrackingWithReturnValue(triggerForAllOidcClientSignals);const payload = lastSignal ? getOidcClientStateChangePayload(lastSignal) : null;if (payload && payload.state === oidcClientStates.LOGGING_OUT) {return <p>Logging out....</p>;}};
Using without React
When React is not used, the beacon and modules can be created and initialized with createLoginSystem(props)
. Props are the same as those passed to LoginProvider. The function returns a beacon instance that holds all modules. There are dozens of utility functions to use without React. See modules and signals.
import { createLoginSystem } from 'hds-react';// Actual properties are omitted from this example to keep it simpleconst loginSystemProperties = {...}const beacon = createLoginSystem(loginSystemProperties);
Packages
Package | Included | Storybook link | Source link |
---|---|---|---|
HDS React | Yes | View in Storybook | View source |
HDS Core | No | - | - |