Core components
Client core
User authentication and authorization
24 min
overview the user authentication and authorization system manages user identity and access control within the ir engine client it verifies user identities through login processes, maintains secure sessions using tokens, and controls access to protected resources based on user permissions by implementing robust authentication and authorization mechanisms, the system ensures that users can only access the features and data they are permitted to use, providing security for sensitive operations like administrative functions this chapter explores the implementation, workflow, and integration of authentication and authorization within the ir engine client core concepts authentication vs authorization authentication and authorization serve distinct but complementary security functions authentication verifies the identity of a user ("who are you?") validates credentials like username/password establishes a secure session with tokens maintains user identity across requests handles login, logout, and session management authorization determines what actions a user can perform ("what can you do?") checks user permissions for specific resources controls access to protected areas like admin panels enforces role based access control restricts operations based on user privileges together, these mechanisms ensure that users are properly identified and can only access appropriate resources identity verification methods the ir engine client supports several authentication methods email and password traditional credential based authentication oauth providers authentication through external services (google, github, etc ) token based sessions maintaining authentication state using jwt (json web tokens) these methods provide flexibility while maintaining security standards permission scopes authorization in the ir engine is managed through permission scopes scopes are specific permissions assigned to users each scope grants access to particular features or operations examples include admin\ access , users\ edit , projects\ create scopes are checked when users attempt to access protected resources this granular approach allows for precise control over user capabilities implementation authentication state the authentication state is managed through hyperflux // simplified from src/user/services/authservice ts import { definestate } from '@ir engine/hyperflux'; // default values for unauthenticated user export const userseed = { id '', name 'guest', // other default user properties }; export const authuserseed = { accesstoken '', // other authentication specific properties }; // define the authentication state export const authstate = definestate({ name 'authstate', initial { isauthenticated false, isprocessing false, error '', authuser authuserseed, // contains accesstoken, etc user userseed // contains user details like id, name } }); this state tracks whether a user is authenticated stores the authentication token maintains user profile information records any authentication errors indicates when authentication is in progress login process the login process authenticates a user and establishes a session // simplified from src/user/services/authservice ts import { api } from '@ir engine/common'; import { getmutablestate } from '@ir engine/hyperflux'; import { authstate } from ' /authstate'; async function handlelogin(email, password) { const authstate = getmutablestate(authstate); // indicate authentication is in progress authstate isprocessing set(true); try { // send credentials to the server via feathersjs const authresult = await api instance authenticate({ strategy 'local', // email/password strategy email email, password password }); // update authstate with the authentication result authstate merge({ isauthenticated true, isprocessing false, error '', authuser { accesstoken authresult accesstoken, // other auth properties }, user authresult user }); console log('login successful'); return authresult; } catch (error) { // handle authentication failure authstate merge({ isauthenticated false, isprocessing false, error error message || 'authentication failed' }); console error('login failed ', error); throw error; } } this function updates the authentication state to indicate processing sends credentials to the server using the feathersjs client if successful, updates the state with the token and user information if unsuccessful, records the error in the state returns the authentication result or throws an error automatic re authentication the client can attempt to re authenticate using a stored token // simplified from src/user/services/authservice ts import { api } from '@ir engine/common'; import { getmutablestate, getstate } from '@ir engine/hyperflux'; import { authstate, userseed, authuserseed } from ' /authstate'; async function tryautologin() { const authstate = getmutablestate(authstate); const existingtoken = getstate(authstate) authuser accesstoken; if (existingtoken) { try { // configure feathersjs to use the existing token await api instance authentication setaccesstoken(existingtoken); // attempt to re authenticate with the server const result = await api instance reauthenticate(); // update authstate with the fresh authentication data authstate merge({ isauthenticated true, error '', authuser result, user result user }); console log('re authenticated successfully'); return result; } catch (error) { // token is invalid or expired console log('token invalid or expired need to log in again '); // reset to unauthenticated state authstate merge({ isauthenticated false, error 'session expired', user userseed, authuser authuserseed }); throw error; } } else { // no token available console log('no existing token user is a guest '); // ensure guest state authstate merge({ isauthenticated false, user userseed, authuser authuserseed }); throw new error('no authentication token'); } } this function checks if an authentication token exists in the state if a token exists, attempts to re authenticate with the server updates the authentication state based on the result handles cases where the token is invalid or missing logout process the logout process terminates the user's session // simplified from src/user/services/authservice ts import { api } from '@ir engine/common'; import { getmutablestate } from '@ir engine/hyperflux'; import { authstate, userseed, authuserseed } from ' /authstate'; async function handlelogout() { const authstate = getmutablestate(authstate); try { // notify the server to invalidate the session await api instance logout(); } catch (error) { console error('error during server logout ', error); // continue with client side logout regardless } finally { // reset authentication state authstate merge({ isauthenticated false, error '', user userseed, authuser authuserseed }); console log('user logged out'); } } this function attempts to notify the server about the logout resets the authentication state to unauthenticated ensures logout completes even if server communication fails authorization checks the client checks user permissions to control access to protected resources // simplified from src/admin/adminroutes tsx import { useeffect } from 'react'; import { usefind } from '@ir engine/common'; import { scopepath } from '@ir engine/common/src/schema type module'; import { engine } from '@ir engine/ecs'; import { getmutablestate, usemutablestate } from '@ir engine/hyperflux'; import { allowedadminroutesstate } from ' /allowedadminroutesstate'; import { routerstate } from ' /common/services/routerservice'; function adminaccesscheck() { const currentuserid = engine instance userid; // fetch user scopes from the server const scopequery = usefind(scopepath, { query { userid currentuserid, paginate false } }); const allowedroutes = usemutablestate(allowedadminroutesstate); useeffect(() => { if (scopequery data) { // check if the user has the admin scope const isadmin = scopequery data find(scope => scope type === 'admin\ admin'); if (isadmin) { console log('user is an admin access granted '); // update allowed routes state to enable admin features // example allowedroutes users access set(true); } else { console log('user is not an admin redirecting '); // redirect to homepage or show an error routerstate navigate('/', { redirecturl '/admin' }); } } }, \[scopequery data, allowedroutes]); // render loading state or admin ui based on access return \<div>checking admin access \</div>; } this component fetches the current user's permission scopes from the server checks if the user has the required admin scope updates the allowed routes state or redirects based on the result renders appropriate ui based on the authorization status authentication workflow the complete authentication workflow follows this sequence sequencediagram participant user as user participant loginui as login ui participant authservice as authservice participant api as api instance participant server as backend server participant userdb as user database user >>loginui enters credentials loginui >>authservice handlelogin(email, password) authservice >>authservice set isprocessing = true authservice >>api authenticate({ strategy 'local', }) api >>server post /authentication server >>userdb verify credentials userdb >>server credentials valid/invalid alt credentials valid server >>server generate jwt token server >>api return { accesstoken, user } api >>authservice return authentication result authservice >>authservice update authstate (isauthenticated = true) authservice >>loginui return success loginui >>user show success, redirect else credentials invalid server >>api return authentication error api >>authservice throw error authservice >>authservice update authstate (error message) authservice >>loginui throw error loginui >>user show error message end this diagram illustrates the user provides credentials through the login ui the authservice processes the login request the api client sends the credentials to the server the server verifies the credentials against the user database if valid, a token is generated and returned the authservice updates the authentication state the ui reflects the authentication result authorization workflow the authorization workflow determines what a user can access sequencediagram participant user as user participant router as router participant adminui as admin component participant api as api instance participant server as backend server participant scopedb as scope database user >>router navigate to /admin router >>adminui render adminaccesscheck adminui >>api usefind(scopepath, { userid }) api >>server get /scopes?userid= server >>scopedb query user scopes scopedb >>server return user scopes server >>api return scope data api >>adminui return scopequery data alt has admin\ admin scope adminui >>adminui update allowedadminroutesstate adminui >>user show admin interface else no admin\ admin scope adminui >>router navigate to homepage router >>user redirect to homepage end this diagram illustrates the user attempts to access a protected route the router renders the component with access check the component fetches the user's scopes from the server the server retrieves the scopes from the database the component checks if the user has the required scope based on the result, the user is either granted access or redirected integration with other components the authentication and authorization system integrates with several other components hyperflux state management authentication state is managed through hyperflux // example of a component using authentication state import { usemutablestate } from '@ir engine/hyperflux'; import { authstate } from ' /user/services/authstate'; function userprofilebutton() { const authstate = usemutablestate(authstate); const isauthenticated = authstate isauthenticated value; const username = authstate user name value; if (isauthenticated) { return \<button>profile {username}\</button>; } else { return \<button>login\</button>; } } this integration allows components to react to authentication state changes provides consistent access to user information enables ui updates based on authentication status feathersjs api services authentication uses feathersjs for server communication // example of authentication with feathersjs import { api } from '@ir engine/common'; // configure feathersjs to include the token with requests api instance configure(authentication({ storagekey 'ir engine jwt', jwtstrategy 'jwt' })); // the token is automatically included in service requests const protecteddata = await api instance service('protected resource') find(); this integration handles token management for api requests ensures authenticated access to protected resources maintains session consistency across service calls client side routing authorization affects navigation and route access // example of route protection import { useeffect } from 'react'; import { usemutablestate } from '@ir engine/hyperflux'; import { authstate } from ' /user/services/authstate'; import { routerstate } from ' /common/services/routerservice'; function protectedroute({ children }) { const authstate = usemutablestate(authstate); const isauthenticated = authstate isauthenticated value; useeffect(() => { if (!isauthenticated) { // redirect to login if not authenticated routerstate navigate('/login', { redirecturl window\ location pathname }); } }, \[isauthenticated]); // only render children if authenticated return isauthenticated ? children null; } this integration controls access to protected routes redirects unauthenticated users to the login page preserves the intended destination for post login redirection benefits of the authentication and authorization system the authentication and authorization system provides several key advantages security protects sensitive operations and data from unauthorized access user identity maintains consistent user identity across the application personalization enables user specific content and features access control provides granular control over feature access administrative functions secures administrative capabilities for authorized users session management handles login, logout, and session persistence integration works seamlessly with other system components these benefits make authentication and authorization essential components of the ir engine client architecture next steps with an understanding of how users are authenticated and authorized, the next chapter explores how the application manages navigation between different views and pages next client side routing and navigation docid\ knnpevk v4 0odysbdgwr