Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New JWT Middleware features and more #1662

Merged
merged 20 commits into from
Nov 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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