Skip to content

Conversation

@goginenibhavani2000
Copy link

@goginenibhavani2000 goginenibhavani2000 commented Aug 11, 2025

Description:

  • Added a declarative RBAC middleware with JSON‑defined roles, flexible role extraction, pattern matching, and helper functions enables consistent, reusable, and easy‑to‑maintain authorization across routes.
  • Addresses RBAC(Role-Based Access Control) Support #2000
  • GoFr has no native RBAC, forcing manual, repetitive route‑level authorization that risks inconsistency and increases maintenance. Expected Benefits include centralized, configurable, and reusable access control that’s easier to maintain, more flexible, and improves security.

Breaking Changes (if applicable):

  • None

Additional Information:

  • No relevant dependencies effected or external libraries used.

Checklist:

  • I have formatted my code using goimport and golangci-lint.
  • All new code is covered by unit tests.
  • This PR does not decrease the overall code coverage.
  • I have reviewed the code comments and documentation for clarity.

Copy link
Member

@coolwednesday coolwednesday left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would like to keep RBAC as a separate module. Hence add go.mod in it. So that it does not get added in the binary of the gofr framework.

@akshat-kumar-singhal
Copy link
Contributor

We would like to keep RBAC as a separate module. Hence add go.mod in it. So that it does not get added in the binary of the gofr framework.

@coolwednesday What's the advantage of having RBAC in another module, unless we move the auth middlewares into a separate module as well?

@Umang01-hash
Copy link
Member

@goginenibhavani2000 please go ahead and resolve the review comments given by the reviewer. While we decide whether to keep it inside or outside like a separate module i think we can focus on completing the implementation, testing, documentation etc. Please let us know if you know any more help.

@Umang01-hash
Copy link
Member

We would like to keep RBAC as a separate module. Hence add go.mod in it. So that it does not get added in the binary of the gofr framework.

@coolwednesday What's the advantage of having RBAC in another module, unless we move the auth middlewares into a separate module as well?

@akshat-kumar-singhal We see RBAC as a layer beneath Auth—more specialized and not universally required. Auth is core to GoFr and widely used, which is why its middlewares are bundled. RBAC, on the other hand, is optional and domain-specific, so we’re keeping it modular to avoid unnecessary coupling and binary weight.

@akshat-kumar-singhal
Copy link
Contributor

akshat-kumar-singhal commented Aug 20, 2025 via email

@Umang01-hash
Copy link
Member

Umang01-hash commented Aug 20, 2025

taken care of by the dead code elimination done during build? I agree that RBAC shouldn’t be a default, but an easy to include module/package. What we could consider is having the interface for RBAC

Yes that makes sense. WE can have the interface in GoFr and implementation outside.

@Umang01-hash
Copy link
Member

@goginenibhavani2000 Are you still working on the issue. Please let us know in case you need any further help for the same.

@goginenibhavani2000
Copy link
Author

@goginenibhavani2000 Are you still working on the issue. Please let us know in case you need any further help for the same.

Yes, i have completed the suggested changes except the change in having interface. Would like to confirm if the RBAC interface is like below

type RBAC interface {
RoleExtractor(r *http.Request, args ...any) (string, error)
IsPathAllowed(role string, path string) bool
}

@coolwednesday
Copy link
Member

@goginenibhavani2000, So Sorry for the delay. I will be prioritising this issue review for the next week's release. Let me know if you are interested to tale this further if not, I myself will pick up this issue.

@goginenibhavani2000
Copy link
Author

@coolwednesday I’d be happy to take this further and help with the changes. Let me know if there's anything specific you'd like me to focus on.

Copy link
Member

@Umang01-hash Umang01-hash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • In all the examples added, if the handlers are not using the ctx so we can keep the signature like :

func handlerName(_ *gofr.Context){}

  • Files like adapter.go, jwt_extractor.go have very less coverage. Can we please try and improve it?

Copy link
Member

@aryanmehrotra aryanmehrotra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code needs to be simplified, once the structural fixes are done code needs to be reviewed again.

Comment on lines +33 to +37
config, err := rbac.LoadPermissions("configs/rbac.json")
if err != nil {
app.Logger().Error("Failed to load RBAC config: ", err)
return
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can do this internally by just taking the file path and also have a default file path so user need to give path unless he gives something different

Comment on lines +42 to +46
"users:read": {"admin", "editor", "viewer"},
"users:write": {"admin", "editor"},
"users:delete": {"admin"},
"posts:read": {"admin", "author", "viewer"},
"posts:write": {"admin", "author"},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

example doesnt seem correct, why author??
also should this not be

admin : users:read, users:write etc etc

to figure out what an admin can do need to look in multiple places

need to think this through what can be a better way

Comment on lines +60 to +65
gofr.WithRoleExtractor(func(req *http.Request, args ...any) (string, error) {
role := req.Header.Get("X-User-Role")
if role == "" {
return "", fmt.Errorf("role header not found")
}
return role, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just take the key? rather than giving user access the complete request? should the role come from anywhere apart from X-User-Role

Also is there any standardised guide which we are following for rbac support? if so we should mention that

Comment on lines +105 to +126
GoFr's RBAC middleware implements a **two-tier authorization system**:

1. **Permission-Based Check** (Primary) - Checks if user's role has the required permission
2. **Role-Based Check** (Fallback) - Falls back to route-based role checking if permission check fails

```go
// Check permission-based access if enabled
if config.EnablePermissions && config.PermissionConfig != nil {
if err := CheckPermission(reqWithRole, config.PermissionConfig); err == nil {
authorized = true
authReason = "permission-based"
}
}

// Check role-based access (if not already authorized by permissions)
if !authorized {
if isRoleAllowed(role, route, config) {
authorized = true
authReason = "role-based"
}
}
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the user make any changes to it?? if cannot what benefit do we get by keeping this in documentation? how is it beneficial to the users of the framework?

Comment on lines +131 to +133
2. **Permission check** → Checks if route has a permission mapping
3. **Role validation** → Verifies if user's role has the required permission
4. **Fallback** → If no permission mapping exists, falls back to role-based check
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the benefit of having the fallback? can't we already do the role-based check in just single operation?

// Array notation - extract first role
app.EnableRBAC(
gofr.WithPermissionsFile("configs/rbac.json"),
gofr.WithJWT("roles[0]"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user role would either be one or many ideally based on implementation, in which case would the user want to get only one role? instead of all and then validate, if required role is there or not.

| **Security** | ⚠️ Low | ✅ High | ⚠️ Low | ✅ High | ✅ High |
| **Flexibility** | ⚠️ Low | ⚠️ Low | ✅ High | ✅ High | ✅✅ Very High |
| **Performance** | ✅ Fast | ✅ Fast | ✅ Fast | ✅ Fast | ⚠️ Slower* |
| **Dynamic Roles** | ❌ No | ❌ No | ❌ No | ❌ No | ✅ Yes |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even without giving db access dynamic roles can still be set, depends on how its implmented

}

// RBACOption is a function that configures RBACOptions.
type RBACOption func(*RBACOptions)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use an interface, similar to options in http service

Comment on lines +32 to +44
func (*auditLogger) logAccess(logger Logger, req *http.Request, role, route string, allowed bool, reason string) {
if logger == nil {
return // Skip logging if no logger provided
}

status := "denied"
if allowed {
status = "allowed"
}

logger.Infof("[RBAC Audit] %s %s - Role: %s - Route: %s - %s - Reason: %s",
req.Method, req.URL.Path, role, route, status, reason)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just keep it by default? without creating this function, what benefit it gives inspite of not logging? and should we not by default log it may be debug level?


// _ is a package-level variable that triggers registration when the package is imported.
// This avoids using init() while maintaining automatic registration behavior.
var _ = registerRBAC()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no need of this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants