Authorization Comprehensive Guide to Authorization with Auth Manager
The Auth Manager class in iamjs provides a robust framework for implementing and managing authorization in your application. It allows for fine-grained access control based on roles and their associated permissions. This guide will walk you through the process of setting up and using the Auth Manager for effective authorization management.
The first step in setting up your authorization system is to define roles. Each role represents a set of permissions for various resources and actions.
import { Role, Schema, AuthManager } from ' @iamjs/core ' ;
const roles = {
user : new Role ( {
name : ' user ' ,
description : ' Standard user with limited permissions ' ,
config : {
user : {
base : ' -r--l ' ,
custom : {
updateOwnProfile : true ,
changePassword : true
}
},
post : {
base : ' cr-dl ' ,
custom : {
like : true ,
comment : true
}
}
}
} ) ,
moderator : new Role ( {
name : ' moderator ' ,
description : ' User with elevated permissions for content moderation ' ,
config : {
user : {
base : ' r---l ' ,
custom : {
banUser : true ,
viewUserDetails : true
}
},
post : {
base : ' crudl ' ,
custom : {
pinPost : true ,
flagInappropriate : true
}
}
}
} ) ,
admin : new Role ( {
name : ' admin ' ,
description : ' Administrator with full system access ' ,
config : {
user : {
base : ' crudl ' ,
custom : {
assignRoles : true ,
viewAuditLogs : true
}
},
post : {
base : ' crudl ' ,
custom : {
deleteAnyPost : true ,
editAnyPost : true
}
},
system : {
base : ' crudl ' ,
custom : {
configureSettings : true ,
viewAnalytics : true
}
}
}
} )
} ;
In this example, we've defined three roles: user
, moderator
, and admin
. Each role has specific permissions for different resources (user, post, system) and actions (create, read, update, delete, list, and custom actions).
Once roles are defined, we create an authorization schema using the Schema
class:
const schema = new Schema ( { roles } );
This schema encapsulates all the defined roles and their permissions.
With the schema in place, we can now initialize the Auth Manager:
const authManager = new AuthManager ( schema );
The Auth Manager will use this schema to perform authorization checks.
The Auth Manager provides an authorize
method for performing authorization checks. This method is highly flexible and can be used in various scenarios:
const canUserCreatePost = authManager . authorize ( {
role : ' user ' ,
actions : [ ' create ' ] ,
resources : ' post '
} );
console . log ( ' Can user create post? ' , canUserCreatePost); // true
const canModeratorManagePosts = authManager . authorize ( {
role : ' moderator ' ,
actions : [ ' update ' , ' delete ' , ' pinPost ' ] ,
resources : [ ' post ' ]
} );
console . log ( ' Can moderator manage posts? ' , canModeratorManagePosts); // true
Strict mode ensures that all specified actions are authorized for all specified resources:
const canAdminDoEverything = authManager . authorize ( {
role : ' admin ' ,
actions : [ ' create ' , ' read ' , ' update ' , ' delete ' , ' list ' , ' assignRoles ' , ' configureSettings ' ] ,
resources : [ ' user ' , ' post ' , ' system ' ] ,
strict : true
} );
console . log ( ' Can admin do everything? ' , canAdminDoEverything); // true
The Auth Manager can construct roles on the fly from provided data:
const dynamicRole = {
name : ' dynamicRole ' ,
config : {
customResource : {
base : ' cr--l ' ,
custom : {
specialAction : true
}
}
}
} ;
const canDynamicRolePerformSpecialAction = authManager . authorize ( {
construct : true ,
data : dynamicRole ,
actions : [ ' specialAction ' ] ,
resources : ' customResource '
} );
console . log ( ' Can dynamic role perform special action? ' , canDynamicRolePerformSpecialAction); // true
Granular Permissions : Define roles with fine-grained permissions to allow for flexible access control.
Resource-Action Mapping : Clearly map out which actions are applicable to which resources.
Custom Actions : Utilize custom actions for application-specific operations that don't fit into CRUD.
Role Hierarchy : Consider implementing a role hierarchy where higher roles inherit permissions from lower roles.
Regular Audits : Periodically review and update role permissions to ensure they align with current security requirements.
Performance Optimization : Cache authorization results for frequently checked permissions to improve performance.
While the Auth Manager provides powerful out-of-the-box functionality, you may want to create custom wrappers to better integrate with your application's architecture or to add additional features. Here are some examples of custom wrappers you can create:
This wrapper creates an Express.js middleware for easy integration with Express applications:
import { Request, Response, NextFunction } from ' express ' ;
import { AuthManager, Schema } from ' @iamjs/core ' ;
export function createAuthMiddleware ( authManager : AuthManager , schema : Schema ) {
return function authMiddleware ( req : Request , res : Response , next : NextFunction ) {
const { role , action , resource } = req . body ; // Assume these are passed in the request body
const isAuthorized = authManager . authorize ( {
role ,
actions : [ action ] ,
resources : [ resource ]
} );
if (isAuthorized) {
next ();
} else {
res . status ( 403 ) . json ({ error: ' Unauthorized ' });
}
};
}
// Usage
const authMiddleware = createAuthMiddleware ( authManager , schema );
app . use ( ' /protected-route ' , authMiddleware, ( req , res ) => {
res . json ({ message: ' Access granted ' });
});
This wrapper creates a custom React hook for easy use in React components:
import { useContext } from ' react ' ;
import { AuthManager } from ' @iamjs/core ' ;
// Assume you have an AuthContext that provides the AuthManager instance
const AuthContext = React . createContext < AuthManager | null > ( null );
export function useAuthorization () {
const authManager = useContext ( AuthContext );
if ( ! authManager) {
throw new Error ( ' useAuthorization must be used within an AuthProvider ' );
}
return {
can : ( role : string , action : string , resource : string ) => {
return authManager . authorize ({
role ,
actions: [action] ,
resources: [resource]
});
} ,
canAll : ( role : string , actions : string [], resources : string [] ) => {
return authManager . authorize ({
role ,
actions ,
resources ,
strict: true
});
}
};
}
// Usage in a React component
function ProtectedComponent () {
const { can } = useAuthorization ();
const userRole = ' user ' ; // This should come from your authentication system
if ( can (userRole , ' read ' , ' secretData ' )) {
return < div >Secret data here !</ div > ;
} else {
return < div >Access denied </ div > ;
}
}
This wrapper creates a higher-order function to wrap GraphQL resolvers with authorization checks:
import { AuthManager } from ' @iamjs/core ' ;
export function withAuthorization (
authManager : AuthManager ,
requiredRole : string ,
requiredAction : string ,
resource : string
) {
return function ( resolver : Function ) {
return async function ( parent : any , args : any , context : any , info : any ) {
const userRole = context . user ?. role ; // Assume role is available in the context
const isAuthorized = authManager . authorize ( {
role : userRole ,
actions : [ requiredAction ] ,
resources : [ resource ]
} );
if ( ! isAuthorized) {
throw new Error ( ' Unauthorized ' );
}
return resolver (parent , args , context , info);
};
};
}
// Usage in GraphQL resolvers
const resolvers = {
Query : {
secretData : withAuthorization ( authManager , ' admin ' , ' read ' , ' secretData ' )(
( parent , args , context ) => {
return ' This is secret data ' ;
}
)
}
} ;
If you're using TypeScript with experimental decorators, you can create an authorization decorator:
import { AuthManager } from ' @iamjs/core ' ;
export function Authorize ( authManager : AuthManager , action : string , resource : string ) {
return function ( target : any , propertyKey : string , descriptor : PropertyDescriptor ) {
const originalMethod = descriptor . value ;
descriptor . value = function ( ... args : any [] ) {
const userRole = this . getUserRole (); // Assume this method exists to get the user's role
const isAuthorized = authManager . authorize ( {
role : userRole ,
actions : [ action ] ,
resources : [ resource ]
} );
if ( ! isAuthorized) {
throw new Error ( ' Unauthorized ' );
}
return originalMethod . apply ( this , args);
};
return descriptor;
};
}
// Usage in a class
class UserService {
@ Authorize (authManager, ' create ' , ' user ' )
createUser ( userData : any ) {
// Create user logic here
}
}
These custom wrappers demonstrate how you can adapt the Auth Manager to various architectural patterns and frameworks. By creating these wrappers, you can provide a more intuitive and integrated authorization experience that fits seamlessly with your application's structure.
Remember to adjust these examples to fit your specific use case and error handling requirements. Also, ensure that you're consistently retrieving the user's role from your authentication system in a secure manner.
The Auth Manager provides a powerful and flexible system for managing authorization in your application. By defining clear roles, creating a comprehensive schema, and utilizing the various authorization check options, you can implement a robust access control system that meets the specific needs of your application.
Remember to regularly review and update your authorization rules as your application evolves to maintain optimal security and functionality.