Usage Guide A comprehensive guide to using KoaRoleManager for role-based access control in koa.js applications.
The KoaRoleManager
is a powerful tool for implementing role-based access control (RBAC) in Koa.js applications. It integrates seamlessly with the @ iamjs / core
package to provide robust authorization capabilities. This guide will walk you through setting up, configuring, and using the KoaRoleManager
in various scenarios.
Here's a basic setup to get you started with @iamjs/koa:
import Koa from ' koa ' ;
import Router from ' koa-router ' ;
import { Role, Schema } from ' @iamjs/core ' ;
import { KoaRoleManager } from ' @iamjs/koa ' ;
// Define roles
const userRole = new Role ( {
name : ' user ' ,
config : {
posts : {
base : ' cr--l ' ,
custom : {
like : true ,
comment : true
}
},
profile : {
base : ' crud- ' ,
custom : {
changePassword : true
}
}
}
} );
// Create schema
const schema = new Schema ( {
roles : { user : userRole }
} );
// Initialize KoaRoleManager
const roleManager = new KoaRoleManager ( {
schema ,
async onSuccess ( ctx , next ) {
ctx . status = 200 ;
ctx . body = { message : ' Access granted ' };
await next () ;
},
async onError ( err , ctx , next ) {
console . error ( ' Authorization error: ' , err ) ;
ctx . status = 403 ;
ctx . body = { error : ' Access denied ' };
await next () ;
}
} );
const app = new Koa ();
const router = new Router ();
// Example route with authorization
router . get ( ' /posts ' ,
roleManager . check ({
resources: ' posts ' ,
actions: [ ' read ' , ' list ' ],
role: ' user '
}),
async ( ctx ) => {
ctx . body = { posts: [ ' Post 1 ' , ' Post 2 ' ] };
}
);
app . use (router . routes ()) . use (router . allowedMethods ());
app . listen ( 3000 , () => console . log ( ' Server running on port 3000 ' ));
This setup demonstrates how to define roles, create a schema, initialize the KoaRoleManager, and use it in a Koa route.
For more complex scenarios, you might need to dynamically determine the role or perform additional checks:
import Koa from ' koa ' ;
import Router from ' koa-router ' ;
import { Role, Schema } from ' @iamjs/core ' ;
import { KoaRoleManager } from ' @iamjs/koa ' ;
// ... (previous role and schema setup)
const roleManager = new KoaRoleManager ( { schema } );
// Middleware to fetch user permissions
async function authMiddleware ( ctx : Koa . Context , next : Koa . Next ) {
// In a real app, you'd fetch this from a database or authentication service
ctx . state . user = { id: 1 , role: ' user ' };
ctx . state . permissions = userRole . toObject ();
await next ();
}
const router = new Router ();
router . post ( ' /posts ' ,
authMiddleware,
roleManager . check ({
resources: ' posts ' ,
actions: [ ' create ' ],
strict: true ,
construct: true ,
data : async ( ctx ) => ctx . state . permissions
}),
async ( ctx ) => {
ctx . body = { message: ' Post created successfully ' };
}
);
const app = new Koa ();
app . use (router . routes ()) . use (router . allowedMethods ());
This advanced example shows how to use middleware for authentication and fetching user permissions, and how to use the construct
and data
options for dynamic role checking.
Customize success and error handling to tailor the behavior of your application:
const roleManager = new KoaRoleManager ( {
schema ,
async onSuccess ( ctx , next ) {
console . log ( ' Authorization successful ' ) ;
ctx . set ( ' X-Auth-Status ' , ' success ' ) ;
ctx . status = 200 ;
ctx . body = { message : ' Access granted ' };
await next () ;
},
async onError ( err , ctx , next ) {
console . error ( ' Authorization error: ' , err ) ;
ctx . status = 403 ;
ctx . body = {
error : ' Access denied ' ,
details : err . message
};
await next () ;
}
} );
Use the onActivity
handler to log or process user activities:
const roleManager = new KoaRoleManager ( {
schema ,
async onSuccess ( ctx , next ) { /* ... */ },
async onError ( err , ctx , next ) { /* ... */ },
async onActivity ( data ) {
console . log ( ' User activity: ' , {
timestamp : new Date () ,
userId : data . ctx ?. state . user ?. id ,
role : data . role ,
resources : data . resources ,
actions : data . actions ,
success : data . success
} ) ;
// In a real app, you might save this to a database or send to a logging service
}
} );
@iamjs/koa provides full TypeScript support. You can create custom interfaces to extend the Koa context:
import Koa from ' koa ' ;
import Router from ' koa-router ' ;
import { Role, Schema } from ' @iamjs/core ' ;
import { KoaRoleManager } from ' @iamjs/koa ' ;
interface CustomState extends Koa . DefaultState {
user : { id : number ; role : string };
permissions : any ;
}
interface CustomContext extends Koa . Context {
state : CustomState ;
}
const roleManager = new KoaRoleManager ( {
schema ,
async onSuccess ( ctx : CustomContext , next ) {
console . log ( ' Authorized user: ' , ctx . state . user . id ) ;
ctx . status = 200 ;
ctx . body = { message : ' Access granted ' };
await next () ;
},
async onError ( err : Error , ctx : CustomContext , next ) {
console . error ( ' Authorization failed for user: ' , ctx . state . user . id ) ;
ctx . status = 403 ;
ctx . body = { error : ' Access denied ' };
await next () ;
},
} );
const router = new Router < CustomState , CustomContext > ();
router . get (
' /protected ' ,
roleManager . check ({
resources: ' protectedResource ' ,
actions: [ ' read ' ],
construct: true ,
data : async ( ctx : CustomContext ) => ctx . state . permissions ,
}),
async ( ctx ) => {
ctx . body = {
message: ' Protected resource accessed ' ,
userId: ctx . state . user . id ,
};
},
);
The checkFn
method allows you to perform authorization checks without the Koa middleware context. This is particularly useful for:
Background jobs
API-based applications
Serverless functions
Any scenario where you need programmatic access control outside of a Koa request/response cycle
import { KoaRoleManager, Schema, Role } from ' @iamjs/koa ' ;
// Define roles and schema
const userRole = new Role ( {
name : ' user ' ,
config : {
documents : {
base : ' r--l ' ,
custom : {
download : true
}
}
}
} );
const adminRole = new Role ( {
name : ' admin ' ,
config : {
documents : {
base : ' crudl ' ,
custom : {
delete : true
}
}
}
} );
const schema = new Schema ( {
roles : { user : userRole , admin : adminRole }
} );
// Initialize KoaRoleManager
const roleManager = new KoaRoleManager ( { schema } );
// Headless check function
async function canAccessDocument ( userId : string , documentId : string , action : string ) : Promise < boolean > {
const userRole = await getUserRole ( userId ); // Implement this function to fetch user's role
return await roleManager . checkFn ({
role: userRole ,
resources: ' documents ' ,
actions: action
});
}
// Usage in a background job
async function processDocuments () {
const documents = await fetchDocumentsToProcess ();
for ( const doc of documents) {
if ( await canAccessDocument (doc . ownerId , doc . id , ' read ' )) {
await processDocument (doc);
} else {
console . log ( ` User ${ doc . ownerId } does not have permission to read document ${ doc . id } ` );
}
}
}
import { KoaRoleManager, Schema } from ' @iamjs/koa ' ;
const schema = new Schema ( { /* ... */ } );
const roleManager = new KoaRoleManager ( { schema } );
async function checkDynamicPermission ( userId : string , resource : string , action : string ) : Promise < boolean > {
const userPermissions = await fetchUserPermissions ( userId );
return await roleManager . checkFn ({
resources: resource ,
actions: action ,
construct: true ,
data: userPermissions
});
}
// Usage in an API endpoint (outside of Koa middleware)
async function handleApiRequest ( userId : string , resource : string , action : string ) {
const canAccess = await checkDynamicPermission ( userId , resource , action );
if (canAccess) {
return { status: ' success ' , message: ' Access granted ' };
} else {
return { status: ' error ' , message: ' Access denied ' };
}
}
import { KoaRoleManager, Schema, Role } from ' @iamjs/koa ' ;
const schema = new Schema ( { /* ... */ } );
const roleManager = new KoaRoleManager ( { schema } );
export async function handleDocumentAccess ( event , context ) {
const { userId , documentId , action } = JSON . parse ( event . body );
const userRole = await getUserRoleFromDatabase ( userId );
const canAccess = await roleManager . checkFn ( {
role : userRole ,
resources : ' documents ' ,
actions : action
} );
if (canAccess) {
// Perform the action
return {
statusCode: 200 ,
body: JSON . stringify ({ message: ' Access granted ' })
};
} else {
return {
statusCode: 403 ,
body: JSON . stringify ({ error: ' Access denied ' })
};
}
}
async function batchProcessDocuments ( userIds : string [], action : string ) {
const results = await Promise . all (
userIds . map ( async ( userId ) => {
const userRole = await getUserRole ( userId ) ;
const canAccess = await roleManager . checkFn ( {
role : userRole ,
resources : ' documents ' ,
actions : action
} ) ;
return { userId , canAccess };
} )
);
return results . filter ( result => result . canAccess ) . map ( result => result . userId );
}
// Usage
const userIdsWithAccess = await batchProcessDocuments ([ ' user1 ' , ' user2 ' , ' user3 ' ] , ' read ' );
console . log ( ' Users with read access: ' , userIdsWithAccess);
These examples demonstrate how checkFn
can be used in various "headless" scenarios, providing flexible and powerful authorization checks outside of the typical Koa middleware flow. This approach allows you to implement consistent access control across different parts of your application, from web routes to background jobs and serverless functions.
By leveraging checkFn
, you can extend your authorization logic beyond just HTTP requests handled by Koa, ensuring a consistent authorization model throughout your entire application ecosystem.
Granular Permissions : Define roles with specific, granular permissions for flexible access control.
Dynamic Role Assignment : Utilize the construct
and data
options for dynamic role assignment based on user data.
Error Handling : Provide clear error messages in your onError
handler for better debugging and user experience.
Logging : Use the onActivity
handler for comprehensive audit trails of authorization attempts.
Performance : Consider caching role data or authorization results for improved performance with complex permission structures.
Security : Always validate and sanitize user input, especially when constructing roles dynamically.
Testing : Implement thorough unit and integration tests for your authorization logic.
Middleware Composition : Leverage Koa's middleware composition to combine authorization checks with other middlewares seamlessly.
Context Usage : Make effective use of Koa's context object to pass data between middlewares and your authorization checks.