Skip to content

Commit cc702a2

Browse files
authored
docs: document and exemplify route-based authorization with Gin and JWT (#356)
- Add comprehensive documentation and examples for the Authorizer function, including its usage patterns, function signature, role/path/method-based access control, recommendations, and advanced usage in English, Simplified Chinese, and Traditional Chinese - Introduce a new example project that demonstrates route-based authorization using Gin and JWT, including example `go.mod`, `go.sum`, and a fully working `main.go` with three-tier access (admin, user, guest) and related route protection logic - Expand README navigation with sections for Authorizer understanding, practical examples, and best practices Signed-off-by: appleboy <[email protected]>
1 parent 163bda7 commit cc702a2

File tree

6 files changed

+1290
-0
lines changed

6 files changed

+1290
-0
lines changed

README.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ Easily add login, token refresh, and authorization to your Gin applications.
4141
- [Refresh Token](#refresh-token)
4242
- [Hello World](#hello-world)
4343
- [Authorization Example](#authorization-example)
44+
- [Understanding the Authorizer](#understanding-the-authorizer)
45+
- [How Authorizer Works](#how-authorizer-works)
46+
- [Authorizer Function Signature](#authorizer-function-signature)
47+
- [Basic Usage Examples](#basic-usage-examples)
48+
- [Example 1: Role-Based Authorization](#example-1-role-based-authorization)
49+
- [Example 2: Path-Based Authorization](#example-2-path-based-authorization)
50+
- [Example 3: Method and Path Based Authorization](#example-3-method-and-path-based-authorization)
51+
- [Setting Up Different Authorization for Different Routes](#setting-up-different-authorization-for-different-routes)
52+
- [Method 1: Multiple Middleware Instances](#method-1-multiple-middleware-instances)
53+
- [Method 2: Single Authorizer with Path Logic](#method-2-single-authorizer-with-path-logic)
54+
- [Advanced Authorization Patterns](#advanced-authorization-patterns)
55+
- [Using Claims for Fine-Grained Control](#using-claims-for-fine-grained-control)
56+
- [Common Patterns and Best Practices](#common-patterns-and-best-practices)
57+
- [Complete Example](#complete-example)
4458
- [Logout](#logout)
4559
- [Cookie Token](#cookie-token)
4660
- [Login request flow (using the LoginHandler)](#login-request-flow-using-the-loginhandler)
@@ -597,6 +611,222 @@ http -f GET localhost:8000/auth/hello "Authorization:Bearer xxxxxxxxx" "Content
597611
}
598612
```
599613

614+
---
615+
616+
## Understanding the Authorizer
617+
618+
The `Authorizer` function is a crucial component for implementing role-based access control in your application. It determines whether an authenticated user has permission to access specific protected routes.
619+
620+
### How Authorizer Works
621+
622+
The `Authorizer` is called **automatically** during the JWT middleware processing for any route that uses `MiddlewareFunc()`. Here's the execution flow:
623+
624+
1. **Token Validation**: JWT middleware validates the token
625+
2. **Identity Extraction**: `IdentityHandler` extracts user identity from token claims
626+
3. **Authorization Check**: `Authorizer` determines if the user can access the resource
627+
4. **Route Access**: If authorized, request proceeds; otherwise, `Unauthorized` is called
628+
629+
### Authorizer Function Signature
630+
631+
```go
632+
func(c *gin.Context, data any) bool
633+
```
634+
635+
- `c *gin.Context`: The Gin context containing request information
636+
- `data any`: User identity data returned by `IdentityHandler`
637+
- Returns `bool`: `true` for authorized access, `false` to deny access
638+
639+
### Basic Usage Examples
640+
641+
#### Example 1: Role-Based Authorization
642+
643+
```go
644+
func authorizeHandler() func(c *gin.Context, data any) bool {
645+
return func(c *gin.Context, data any) bool {
646+
if v, ok := data.(*User); ok && v.UserName == "admin" {
647+
return true // Only admin users can access
648+
}
649+
return false
650+
}
651+
}
652+
```
653+
654+
#### Example 2: Path-Based Authorization
655+
656+
```go
657+
func authorizeHandler() func(c *gin.Context, data any) bool {
658+
return func(c *gin.Context, data any) bool {
659+
user, ok := data.(*User)
660+
if !ok {
661+
return false
662+
}
663+
664+
path := c.Request.URL.Path
665+
666+
// Admin can access all routes
667+
if user.Role == "admin" {
668+
return true
669+
}
670+
671+
// Regular users can only access /auth/profile and /auth/hello
672+
allowedPaths := []string{"/auth/profile", "/auth/hello"}
673+
for _, allowedPath := range allowedPaths {
674+
if path == allowedPath {
675+
return true
676+
}
677+
}
678+
679+
return false
680+
}
681+
}
682+
```
683+
684+
#### Example 3: Method and Path Based Authorization
685+
686+
```go
687+
func authorizeHandler() func(c *gin.Context, data any) bool {
688+
return func(c *gin.Context, data any) bool {
689+
user, ok := data.(*User)
690+
if !ok {
691+
return false
692+
}
693+
694+
path := c.Request.URL.Path
695+
method := c.Request.Method
696+
697+
// Admins have full access
698+
if user.Role == "admin" {
699+
return true
700+
}
701+
702+
// Users can only GET their own profile
703+
if path == "/auth/profile" && method == "GET" {
704+
return true
705+
}
706+
707+
// Users cannot modify or delete resources
708+
if method == "POST" || method == "PUT" || method == "DELETE" {
709+
return false
710+
}
711+
712+
return true // Allow other GET requests
713+
}
714+
}
715+
```
716+
717+
### Setting Up Different Authorization for Different Routes
718+
719+
To implement different authorization rules for different route groups, you can create multiple middleware instances or use path checking within a single Authorizer:
720+
721+
#### Method 1: Multiple Middleware Instances
722+
723+
```go
724+
// Admin-only middleware
725+
adminMiddleware, _ := jwt.New(&jwt.GinJWTMiddleware{
726+
// ... other config
727+
Authorizer: func(c *gin.Context, data any) bool {
728+
if user, ok := data.(*User); ok {
729+
return user.Role == "admin"
730+
}
731+
return false
732+
},
733+
})
734+
735+
// Regular user middleware
736+
userMiddleware, _ := jwt.New(&jwt.GinJWTMiddleware{
737+
// ... other config
738+
Authorizer: func(c *gin.Context, data any) bool {
739+
if user, ok := data.(*User); ok {
740+
return user.Role == "user" || user.Role == "admin"
741+
}
742+
return false
743+
},
744+
})
745+
746+
// Route setup
747+
adminRoutes := r.Group("/admin", adminMiddleware.MiddlewareFunc())
748+
userRoutes := r.Group("/user", userMiddleware.MiddlewareFunc())
749+
```
750+
751+
#### Method 2: Single Authorizer with Path Logic
752+
753+
```go
754+
func authorizeHandler() func(c *gin.Context, data any) bool {
755+
return func(c *gin.Context, data any) bool {
756+
user, ok := data.(*User)
757+
if !ok {
758+
return false
759+
}
760+
761+
path := c.Request.URL.Path
762+
763+
// Admin routes - only admins allowed
764+
if strings.HasPrefix(path, "/admin/") {
765+
return user.Role == "admin"
766+
}
767+
768+
// User routes - users and admins allowed
769+
if strings.HasPrefix(path, "/user/") {
770+
return user.Role == "user" || user.Role == "admin"
771+
}
772+
773+
// Public authenticated routes - all authenticated users
774+
return true
775+
}
776+
}
777+
```
778+
779+
### Advanced Authorization Patterns
780+
781+
#### Using Claims for Fine-Grained Control
782+
783+
```go
784+
func authorizeHandler() func(c *gin.Context, data any) bool {
785+
return func(c *gin.Context, data any) bool {
786+
// Extract additional claims
787+
claims := jwt.ExtractClaims(c)
788+
789+
// Get user permissions from claims
790+
permissions, ok := claims["permissions"].([]interface{})
791+
if !ok {
792+
return false
793+
}
794+
795+
// Check if user has required permission for this route
796+
requiredPermission := getRequiredPermission(c.Request.URL.Path)
797+
798+
for _, perm := range permissions {
799+
if perm.(string) == requiredPermission {
800+
return true
801+
}
802+
}
803+
804+
return false
805+
}
806+
}
807+
808+
func getRequiredPermission(path string) string {
809+
permissionMap := map[string]string{
810+
"/auth/users": "read_users",
811+
"/auth/reports": "read_reports",
812+
"/auth/settings": "admin",
813+
}
814+
return permissionMap[path]
815+
}
816+
```
817+
818+
### Common Patterns and Best Practices
819+
820+
1. **Always validate the data type**: Check if the user data can be cast to your expected type
821+
2. **Use claims for additional context**: Access JWT claims using `jwt.ExtractClaims(c)`
822+
3. **Consider the request context**: Use `c.Request.URL.Path`, `c.Request.Method`, etc.
823+
4. **Fail securely**: Return `false` by default and explicitly allow access
824+
5. **Log authorization failures**: Add logging for debugging authorization issues
825+
826+
### Complete Example
827+
828+
See the [authorization example](_example/authorization/) for a complete implementation showing different authorization scenarios.
829+
600830
### Logout
601831

602832
Login first, then call the logout endpoint with the JWT token:

0 commit comments

Comments
 (0)