Closed devlamine closed 2 years ago
How you authenticate users to the API is one thing. That depends on you're design completely. As for authorizing access to routes based on specific roles I believe the preferred pattern is to create your own middleware for RoleBasedAuth. Then in the Routes.php
you can specify which routes to add your middleware to: $app->get('/adminsOnly', ...)->add(AdminOnlyAuth::class);
Thank you for your answer @DLzer . I have several access levels not only the admin, I have to create each role = midelware ? this is not practical ! sometimes there are routes returned a response depending on the connected user example if (is_admin ()) { return all data } else if (is_editor () { list of editors } ....
I think of it this way: in the middleware I get the role $request = $request-> withAttribute ('role', $role); ?
I understand, you're looking more-so at authenticating the route via middleware. This is ultimately up to you. However, I'll share my approach which is the typical blog method. Lets say you have 3 roles ( Admin, Editor, Reader ). You can implement middleware which will allow 'Role and Above' to access your route.
At the Reader level, locking out routes from Editors, or Admins is typically unnecessary so you can omit the role specific middleware and just authenticate that the user is who they say they are.
For the Editor level you really only want to lock out Readers.. considering Admins should still be able to access all areas that editors can. So you can have EditorMiddleware that specifies only (Editor, Admin) have the capability to access the route, while still authenticating that they are who they say they are.
And for Admins, obviously you want to lock out anyone who is not an admin under AdminMiddleware.
In terms of 'how' you authenticate is up to you. I typically sent a JWT on authentication, and verify/authorize that upon each request to the API. So in theory
{userId: 1, role: "admin"}
I hope this was helpful and/or made sense.
Hi @DLzer , you answered a part of my question which 'is already implemented but not for a route which returns an answer and which must be changed according to the role of the authenticated user. I give you an example maybe I misspoke: I have a route which returns the list of students if a role teacher the route should only return the students of that teacher and if a role director the route should return all the students of the school. Do you recommend the solution to pass the role in the header in the Middleware and process the response according to the role?
in Middleware
$request = $request->withAttribute('role', $role); ?
in Route
$role= $request->getAttribute('role')
if($role==='teacher'){
//get list students of that teacher
}
if($role==='director'){
//get list all the students of the school.
}
Thank you for your advice !
In my opinion I would not append attributes at the middleware level. You want to keep each component as simple as possible while following the single responsibility principle. In that case I would have by database set up like:
x_students
x_teachers
x_students_to_teachers
With the x_students_to_teachers
being a join based on a constraint column like x_students.teacher_id = x.teachers.id
. Then I would have the routes set up like so:
/students/{id}
-> StudentReaderAction -> StudentReaderService <- Returns a single student
/students/teacher/{id}
-> StudentFinderAction -> StudentFinderService <- Returns a list of students based on teacher ID
/students
-> StudentFinderAction -> StudentFinderService <- Returns a list of all students
Where the StudentFinderService is more-so parsing a constant ( the route ) instead of accepting something that is variable a role attribute.
Then your middleware could look like:
$app->group('/students', function( RouteCollectorProxy( $app ) {
// Only director ( or above ) can access
$app->get('', App\Action\StudentFinderAction::class)->add(DirectorMiddleware);
// Only specific teacher ( or above ) can access
$app->get('/teacher/{id}', App\Action\StudentFinderAction::class)->add(TeacherMiddleware);
});
Hope that clarified a bit more.
Yes I like that it is cleaner but it will generate another problem lol, side app client I have to check if a director? I call the route
/students/
and if a teacher I call the function /students/teacher/{id}
?
I'm sorry I can't comment to much on client-side development. However, I can say while working with my front end devs that typically you would want to use the JWT returned from authenticating the user to determine role, and create 'Role dependent components' based off of that. An Admin should have a different dashboard then a Directory, and same with a Director verses a Teacher.
Edit to clarify a little more thoroughly:
If you're using something like Angular or React you should have a service that manages the users state throughout the app. Your components can be layers accordingly to the role given by the user scope:
DashboardComponent
( Which is a wrapper )DirectorDashboard
( Shows metrics and other material separate from a teacher )TeacherDashboard
( Shows student metrics, and separate material from a director )In your DashboardComponent
you can determine which role the apps state is holding onto using your role service and pull in the correct component that way.
userService.getById(currentUser.id).then(userFromApi => this.setState({ userFromApi }));
render() {
const { currentUser, userFromApi } = this.state;
return(
{ if ( currentUser.role === constants.ADMIN_ROLE ) { return(AdminComponent) } }
{ if ( currentUser.role === constants.DIRECTOR_ROLE) { return(DirectorComponent) }
{ if ( currentUser.role === constants.TEACHER_ROLE) { return(TeacherComponent) }
)
}
This react example is obviously stripped down to show proof of concept.
Great job doing such clean middleware, components, thank you!
Hello, I hope all is well with you.
What is the best way to implement a role based authentication and authorization system in slim4-api-skeleton
My app will have three different users - (admin, Author,Editor ...). I want to configure allowed groups to a route ?
Thanks