-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
pperalta
committed
Feb 25, 2019
1 parent
ba29ab8
commit 3830214
Showing
1 changed file
with
89 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,81 +1,114 @@ | ||
# go-jwt-tools | ||
|
||
Golang authorization middleware for JWT tokens. JWT tools (auth0 or other) | ||
Golang authorization http middleware for Authorization headers and a | ||
|
||
There are two important features on this package: | ||
- `authorization.go` contains a middleware that processes a token and checks its validity (authorizes). | ||
- `permissions.go` handles the "PermissionsTable" struct which contains the information of the JWT token conveniently adapted, and a set of functions to use it. | ||
These are the important features on this package: | ||
|
||
## MiddleWare | ||
### How to use | ||
- `User`: The object representation of who is doing the request and its permissions. | ||
|
||
```go | ||
type User struct { | ||
AuthorizationValue string | ||
IsDummy bool | ||
Permissions Permissions | ||
} | ||
|
||
type Permissions interface { | ||
// CheckPermission returns the given Permissions for a given product and object. Returns the special Permissions applied on that object if any, and a boolean indicating if the user has the requested Permission. NOTE: Special Permissions returned can be filtered by the specials argument). | ||
CheckPermission(product string, object string, permission Permission, specials ...string) ([]string, bool) | ||
// ValidGroups returns all the groups and its Permissions that have any Permission for the given product and object. | ||
ValidGroups(product string, object string, permission Permission) map[string]struct{} | ||
// Returns all groups of a given type | ||
GetGroups(groupType string) []string | ||
// GetAllGroups returns the group hierarchy | ||
GetAllGroups() map[string]struct{} | ||
// GetGroupsByTypes returns a map indexed by group types, containing the list of groups of that type | ||
GetGroupsByTypes() map[string][]string | ||
// GetParents returns all the parent groups of a given group. | ||
GetParents(group string) map[string]interface{} | ||
} | ||
|
||
We just need to add a call to the function **`Authorize`** on all the calls that must be authorized (in this case, we use a **`Route`** struct that contains the `HandlerFunc` and a `bool` indicating if that `Route` must be authorized). `Authorize` expects the handler function to wrap and a configuration object of type **`Config`** (defined on `authorization.go` file). | ||
``` | ||
|
||
**IMPORTANT**: The middleware stores the `PermissionTable` item on the [context](https://golang.org/pkg/context/), under the key defined on the `ContextKey` constant. | ||
- `Parser`: Who knows how to transform an authorization header into an User, this is what the different authorization techniques should implement. | ||
|
||
### Example of use | ||
```go | ||
type Parser interface { | ||
Parse(authHeader string) (*User, error) | ||
} | ||
``` | ||
- `Middleware`: A middleware is a wrap to a **http.Handler** | ||
|
||
```golang | ||
func NewRouter() *mux.Router { | ||
router := mux.NewRouter().StrictSlash(true) | ||
```go | ||
func(h http.Handler) http.Handler | ||
``` | ||
|
||
// Prepare Authorization configuration | ||
c := authorization.Config{ | ||
PublicKeyStr: "myKey", | ||
AdminGroup: "admin", | ||
IgnoreExpiration: false, | ||
TokenDummy: "TokenDummy", | ||
} | ||
This concrete middleware requires a **Parser** that will be used to transform the Authorization header from **http.Request** into an **User**, and then put in the request context. To retrieve the **User** from the context, use the function: | ||
|
||
for _, route := range routes { | ||
var handler http.Handler | ||
```go | ||
func UserFromContext(ctx context.Context) (*User, bool) | ||
``` | ||
|
||
// Add Authorization or not | ||
if route.Authorization { | ||
handler = authorization.Authorize(route.HandlerFunc(), c) | ||
|
||
} else { | ||
handler = route.HandlerFunc() | ||
} | ||
### Implementations | ||
|
||
handler = handlers.CompressHandler(util.CompressGzip(handler, route.GzipMandatory)) | ||
Implementation details can be found in these subpackages: | ||
|
||
router. | ||
Methods(route.Method). | ||
Path(route.Pattern). | ||
Name(route.Name). | ||
Handler(handler) | ||
} | ||
#### jwt | ||
|
||
return router | ||
In this implementation, the Authorization headers must be **Bearers** (auth0 or other). | ||
|
||
The jwt parser can be instantiated from: | ||
|
||
```go | ||
type ParserConfig struct { | ||
PublicKey string | ||
AdminGroup string | ||
DummyToken string | ||
IgnoreExpiration bool | ||
MemberIDClaim []string | ||
GroupsClaim []string | ||
} | ||
``` | ||
|
||
After this, out `PermissionTable` will be stored on the `ContextKey` key of the context: | ||
#### cache | ||
|
||
```golang | ||
permissions := ctx.Value(authorization.ContextKey).(*authorization.PermissionTable) | ||
``` | ||
Has a Parser implementation that uses a [lru cache](https://github.com/travelgateX/go-cache) where the key is the Authorization header and the value is the User, it basically caches the Parsing process. Recommended when the parsing process is heavy. | ||
|
||
### How to use | ||
|
||
## Permissions | ||
First instance the desired Parser implementation, for instance, if we want our endpoint to understand of jwt bearers: | ||
|
||
```go | ||
jwtParserConfig := jwt.ParserConfig{ | ||
AdminGroup: "admin", | ||
PublicKey: "myKey", | ||
DummyToken: "dummyToken", | ||
IgnoreExpiration: false, | ||
GroupsClaim: []string{"https://xtg.com/iam", "https://travelgatex.com/iam"}, | ||
MemberIDClaim: []string{"https://xtg.com/member_id", "https://travelgatex.com/member_id"}, | ||
} | ||
|
||
```golang | ||
jwtParser := jwt.NewParser(jwtParserConfig) | ||
``` | ||
|
||
type Permissions interface { | ||
// CheckPermission returns the given permissions for a given product and object. Returns the special permissions applied on that object if any, and a boolean indicating if the user has the requested permission. NOTE: Special permissions returned can be filtered by the specials argument). | ||
CheckPermission(product string, object string, per string, specials ...string) ([]string, bool) | ||
// ValidGroups returns all the groups and its permissions that have any permission for the given product and object. | ||
ValidGroups(product string, object string, per string) map[string]bool | ||
// Returns all groups of a given type | ||
GetGroups(groupType string) []string | ||
// GetAllGroups returns the group hierarchy | ||
GetAllGroups() map[string]struct{} | ||
// GetGroupsByTypes returns a map indexed by group types, containing the list of groups of that type | ||
GetGroupsByTypes() map[string][]string | ||
// GetParents returns all the parent groups of a given group. | ||
GetParents(group string) map[string]interface{} | ||
} | ||
Then, if we want a cache layer to cache the jwt parsing process we can wrap the **jwtParser** with a cache parser: | ||
|
||
```go | ||
size := 100 | ||
ttl := time.Minute | ||
c, _ := cache.New(size, ttl) | ||
cacheParser := authcache.NewParser(jwtParser, c) | ||
``` | ||
|
||
Now that we have built the desired parser **cacheParser**, instance the middleware and use it to wrap your service handler: | ||
|
||
```go | ||
middleware := authorization.Middleware(cacheParser) | ||
|
||
var serviceHandler http.Handler // omitted code | ||
serviceHandler = middleware(serviceHandler) | ||
|
||
http.Handle("/foo", serviceHandler) | ||
``` | ||
|
||
Remember that in order to obtain the **User**, you must retrieve it from the context.Context using the func **UserFromContext** |