Skip to content

Commit

Permalink
Merge pull request #1662 from kataras/jwt-new-features
Browse files Browse the repository at this point in the history
New JWT Middleware features and more
  • Loading branch information
kataras authored Nov 6, 2020
2 parents 3ed3aed + f049c51 commit 3d5ed99
Show file tree
Hide file tree
Showing 61 changed files with 2,910 additions and 1,391 deletions.
10 changes: 6 additions & 4 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The codebase for Dependency Injection, Internationalization and localization and
## Fixes and Improvements

- A generic User interface, see the `Context.SetUser/User` methods in the New Context Methods section for more. In-short, the basicauth middleware's stored user can now be retrieved through `Context.User()` which provides more information than the native `ctx.Request().BasicAuth()` method one. Third-party authentication middleware creators can benefit of these two methods, plus the Logout below.
- A `Context.Logout` method is added, can be used to invalidate [basicauth](https://github.com/kataras/iris/blob/master/_examples/auth/basicauth/main.go) client credentials.
- A `Context.Logout` method is added, can be used to invalidate [basicauth](https://github.com/kataras/iris/blob/master/_examples/auth/basicauth/main.go) or [jwt](https://github.com/kataras/iris/blob/master/_examples/auth/jwt/overview/main.go) client credentials.
- Add the ability to [share functions](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) between handlers chain and add an [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-services) on sharing Go structures (aka services).

- Add the new `Party.UseOnce` method to the `*Route`
Expand Down Expand Up @@ -262,7 +262,7 @@ var dirOpts = iris.DirOptions{

- New builtin [requestid](https://github.com/kataras/iris/tree/master/middleware/requestid) middleware.

- New builtin [JWT](https://github.com/kataras/iris/tree/master/middleware/jwt) middleware based on [square/go-jose](https://github.com/square/go-jose) featured with optional encryption to set claims with sensitive data when necessary.
- New builtin [JWT](https://github.com/kataras/iris/tree/master/middleware/jwt) middleware based on the fastest JWT implementation; [kataras/jwt](https://github.com/kataras/jwt) featured with optional wire encryption to set claims with sensitive data when necessary.

- New `iris.RouteOverlap` route registration rule. `Party.SetRegisterRule(iris.RouteOverlap)` to allow overlapping across multiple routes for the same request subdomain, method, path. See [1536#issuecomment-643719922](https://github.com/kataras/iris/issues/1536#issuecomment-643719922). This allows two or more **MVC Controllers** to listen on the same path based on one or more registered dependencies (see [_examples/mvc/authenticated-controller](https://github.com/kataras/iris/tree/master/_examples/mvc/authenticated-controller)).

Expand Down Expand Up @@ -310,13 +310,14 @@ var dirOpts = iris.DirOptions{

## New Context Methods

- `Context.ReadURL(ptr interface{}) error` shortcut of `ReadParams` and `ReadQuery`. Binds URL dynamic path parameters and URL query parameters to the given "ptr" pointer of a struct value.
- `Context.SetUser(User)` and `Context.User() User` to store and retrieve an authenticated client. Read more [here](https://github.com/iris-contrib/middleware/issues/63).
- `Context.SetLogoutFunc(fn interface{}, persistenceArgs ...interface{})` and `Logout(args ...interface{}) error` methods to allow different kind of auth middlewares to be able to set a "logout" a user/client feature with a single function, the route handler may not be aware of the implementation of the authentication used.
- `Context.SetFunc(name string, fn interface{}, persistenceArgs ...interface{})` and `Context.CallFunc(name string, args ...interface{}) ([]reflect.Value, error)` to allow middlewares to share functions dynamically when the type of the function is not predictable, see the [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) for more.
- `Context.TextYAML(interface{}) error` same as `Context.YAML` but with set the Content-Type to `text/yaml` instead (Google Chrome renders it as text).
- `Context.IsDebug() bool` reports whether the application is running under debug/development mode. It is a shortcut of Application.Logger().Level >= golog.DebugLevel.
- `Context.IsRecovered() bool` reports whether the current request was recovered from the [recover middleware](https://github.com/kataras/iris/tree/master/middleware/recover). Also the `iris.IsErrPrivate` function and `iris.ErrPrivate` interface have been introduced.
- `Context.RecordBody()` same as the Application's `DisableBodyConsumptionOnUnmarshal` configuration field but registers per chain of handlers. It makes the request body readable more than once.
- `Context.IsRecovered() bool` reports whether the current request was recovered from the [recover middleware](https://github.com/kataras/iris/tree/master/middleware/recover). Also the `Context.GetErrPublic() (bool, error)`, `Context.SetErrPrivate(err error)` methods and `iris.ErrPrivate` interface have been introduced.
- `Context.RecordRequestBody(bool)` same as the Application's `DisableBodyConsumptionOnUnmarshal` configuration field but registers per chain of handlers. It makes the request body readable more than once.
- `Context.IsRecordingBody() bool` reports whether the request body can be readen multiple times.
- `Context.ReadHeaders(ptr interface{}) error` binds request headers to "ptr". [Example](https://github.com/kataras/iris/blob/master/_examples/request-body/read-headers/main.go).
- `Context.ReadParams(ptr interface{}) error` binds dynamic path parameters to "ptr". [Example](https://github.com/kataras/iris/blob/master/_examples/request-body/read-params/main.go).
Expand Down Expand Up @@ -490,6 +491,7 @@ Prior to this version the `iris.Context` was the only one dependency that has be
| [net.IP](https://golang.org/pkg/net/#IP) | `net.ParseIP(ctx.RemoteAddr())` |
| [mvc.Code](https://pkg.go.dev/github.com/kataras/iris/v12/mvc?tab=doc#Code) | `ctx.GetStatusCode() int` |
| [mvc.Err](https://pkg.go.dev/github.com/kataras/iris/v12/mvc?tab=doc#Err) | `ctx.GetErr() error` |
| [iris/context.User](https://pkg.go.dev/github.com/kataras/iris/v12/context?tab=doc#User) | `ctx.User()` |
| `string`, | |
| `int, int8, int16, int32, int64`, | |
| `uint, uint8, uint16, uint32, uint64`, | |
Expand Down
6 changes: 3 additions & 3 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ Revision ID: 5fc50a00491616d5cd0cbce3abd8b699838e25ca
toml 3012a1dbe2e4bd1 https://github.com/BurntSushi/toml
391d42b32f0577c
b7bbc7f005
jose d84c719419c2a90 https://github.com/square/go-jose
8d188ea67e09652
f5c1929ae8
jwt 5f34e0a4e28178b https://github.com/kataras/jwt
3781df69552bdc5
481a0d4bef
uuid cb32006e483f2a2 https://github.com/google/uuid
3230e24209cf185
c65b477dbf
10 changes: 8 additions & 2 deletions _examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,9 @@
* [Bind Form](request-body/read-form/main.go)
* [Checkboxes](request-body/read-form/checkboxes/main.go)
* [Bind Query](request-body/read-query/main.go)
* [Bind Headers](request-body/read-headers/main.go)
* [Bind Params](request-body/read-params/main.go)
* [Bind URL](request-body/read-url/main.go)
* [Bind Headers](request-body/read-headers/main.go)
* [Bind Body](request-body/read-body/main.go)
* [Bind Custom per type](request-body/read-custom-per-type/main.go)
* [Bind Custom via Unmarshaler](request-body/read-custom-via-unmarshaler/main.go)
Expand Down Expand Up @@ -197,8 +198,12 @@
* Authentication, Authorization & Bot Detection
* [Basic Authentication](auth/basicauth/main.go)
* [CORS](auth/cors)
* [JWT](auth/jwt/main.go)
* JSON Web Tokens
* [Basic](auth/jwt/basic/main.go)
* [Middleware](auth/jwt/midleware/main.go)
* [Blocklist](auth/jwt/blocklist/main.go)
* [Refresh Token](auth/jwt/refresh-token/main.go)
* [Tutorial](auth/jwt/tutorial)
* [JWT (community edition)](https://github.com/iris-contrib/middleware/tree/v12/jwt/_example/main.go)
* [OAUth2](auth/goth/main.go)
* [Manage Permissions](auth/permissions/main.go)
Expand All @@ -218,6 +223,7 @@
* [Badger](sessions/database/badger/main.go)
* [BoltDB](sessions/database/boltdb/main.go)
* [Redis](sessions/database/redis/main.go)
* [View Data](sessions/viewdata)
* Websocket
* [Gorilla FileWatch (3rd-party)](websocket/gorilla-filewatch/main.go)
* [Basic](websocket/basic)
Expand Down
4 changes: 3 additions & 1 deletion _examples/auth/basicauth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ func h(ctx iris.Context) {
// makes sure for that, otherwise this handler will not be executed.
// OR:
user := ctx.User()
ctx.Writef("%s %s:%s", ctx.Path(), user.GetUsername(), user.GetPassword())
username, _ := user.GetUsername()
password, _ := user.GetPassword
ctx.Writef("%s %s:%s", ctx.Path(), username, password)
}

func logout(ctx iris.Context) {
Expand Down
29 changes: 0 additions & 29 deletions _examples/auth/jwt/README.md

This file was deleted.

78 changes: 78 additions & 0 deletions _examples/auth/jwt/basic/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package main

import (
"time"

"github.com/kataras/iris/v12"
"github.com/kataras/jwt"
)

/*
Learn how to use any JWT 3rd-party package with Iris.
In this example we use the kataras/jwt one.
Install with:
go get -u github.com/kataras/jwt
Documentation:
https://github.com/kataras/jwt#table-of-contents
*/

// Replace with your own key and keep them secret.
// The "signatureSharedKey" is used for the HMAC(HS256) signature algorithm.
var signatureSharedKey = []byte("sercrethatmaycontainch@r32length")

func main() {
app := iris.New()

app.Get("/", generateToken)
app.Get("/protected", protected)

app.Listen(":8080")
}

type fooClaims struct {
Foo string `json:"foo"`
}

func generateToken(ctx iris.Context) {
claims := fooClaims{
Foo: "bar",
}

// Sign and generate compact form token.
token, err := jwt.Sign(jwt.HS256, signatureSharedKey, claims, jwt.MaxAge(10*time.Minute))
if err != nil {
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}

tokenString := string(token) // or jwt.BytesToString
ctx.HTML(`Token: ` + tokenString + `<br/><br/>
<a href="/protected?token=` + tokenString + `">/protected?token=` + tokenString + `</a>`)
}

func protected(ctx iris.Context) {
// Extract the token, e.g. cookie, Authorization: Bearer $token
// or URL query.
token := ctx.URLParam("token")
// Verify the token.
verifiedToken, err := jwt.Verify(jwt.HS256, signatureSharedKey, []byte(token))
if err != nil {
ctx.StopWithStatus(iris.StatusUnauthorized)
return
}

ctx.Writef("This is an authenticated request.\n\n")

// Decode the custom claims.
var claims fooClaims
verifiedToken.Claims(&claims)

// Just an example on how you can retrieve all the standard claims (set by jwt.MaxAge, "exp").
standardClaims := jwt.GetVerifiedToken(ctx).StandardClaims
expiresAtString := standardClaims.ExpiresAt().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
timeLeft := standardClaims.Timeleft()

ctx.Writef("foo=%s\nexpires at: %s\ntime left: %s\n", claims.Foo, expiresAtString, timeLeft)
}
101 changes: 101 additions & 0 deletions _examples/auth/jwt/blocklist/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package main

import (
"time"

"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
"github.com/kataras/iris/v12/middleware/jwt/blocklist/redis"

// Optionally to set token identifier.
"github.com/google/uuid"
)

var (
signatureSharedKey = []byte("sercrethatmaycontainch@r32length")

signer = jwt.NewSigner(jwt.HS256, signatureSharedKey, 15*time.Minute)
verifier = jwt.NewVerifier(jwt.HS256, signatureSharedKey)
)

type userClaims struct {
Username string `json:"username"`
}

func main() {
app := iris.New()

// IMPORTANT
//
// To use the in-memory blocklist just:
// verifier.WithDefaultBlocklist()
// To use a persistence blocklist, e.g. redis,
// start your redis-server and:
blocklist := redis.NewBlocklist()
// To configure single client or a cluster one:
// blocklist.ClientOptions.Addr = "127.0.0.1:6379"
// blocklist.ClusterOptions.Addrs = []string{...}
// To set a prefix for jwt ids:
// blocklist.Prefix = "myapp-"
//
// To manually connect and check its error before continue:
// err := blocklist.Connect()
// By default the verifier will try to connect, if failed then it will throw http error.
//
// And then register it:
verifier.Blocklist = blocklist
verifyMiddleware := verifier.Verify(func() interface{} {
return new(userClaims)
})

app.Get("/", authenticate)

protectedAPI := app.Party("/protected", verifyMiddleware)
protectedAPI.Get("/", protected)
protectedAPI.Get("/logout", logout)

// http://localhost:8080
// http://localhost:8080/protected?token=$token
// http://localhost:8080/logout?token=$token
// http://localhost:8080/protected?token=$token (401)
app.Listen(":8080")
}

func authenticate(ctx iris.Context) {
claims := userClaims{
Username: "kataras",
}

// Generate JWT ID.
random, err := uuid.NewRandom()
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
id := random.String()

// Set the ID with the jwt.ID.
token, err := signer.Sign(claims, jwt.ID(id))

if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}

ctx.Write(token)
}

func protected(ctx iris.Context) {
claims := jwt.Get(ctx).(*userClaims)

// To the standard claims, e.g. the generated ID:
// jwt.GetVerifiedToken(ctx).StandardClaims.ID

ctx.WriteString(claims.Username)
}

func logout(ctx iris.Context) {
ctx.Logout()

ctx.Redirect("/", iris.StatusTemporaryRedirect)
}
Loading

0 comments on commit 3d5ed99

Please sign in to comment.