1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-09 13:05:56 +00:00

As noticed in my previous commit, the existing jwt libraries added a lot of performance cost between jwt-featured requests and simple requests. That's why a new custom JWT parser was created. This commit adds our custom jwt parser as the underline token signer and verifier

This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-10-30 22:12:16 +02:00
parent d517f36a29
commit 8eea0296a7
21 changed files with 750 additions and 2431 deletions

View File

@@ -4,41 +4,75 @@ import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
"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()
// With AES-GCM (128) encryption:
// j := jwt.HMAC(15*time.Minute, "secret", "itsa16bytesecret")
// Without extra encryption, just the sign key:
j := jwt.HMAC(15*time.Minute, "secret")
app.Get("/", generateToken(j))
app.Get("/protected", j.VerifyMap(), protected)
app.Get("/", generateToken)
app.Get("/protected", protected)
app.Listen(":8080")
}
func generateToken(j *jwt.JWT) iris.Handler {
return func(ctx iris.Context) {
token, err := j.Token(iris.Map{
"foo": "bar",
})
if err != nil {
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}
type fooClaims struct {
Foo string `json:"foo"`
}
ctx.HTML(`Token: ` + token + `<br/><br/>
<a href="/protected?token=` + token + `">/secured?token=` + token + `</a>`)
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")
claims := jwt.Get(ctx).(iris.Map)
// Decode the custom claims.
var claims fooClaims
verifiedToken.Claims(&claims)
ctx.Writef("foo=%s\n", claims["foo"])
// 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)
}

View File

@@ -0,0 +1,91 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
var (
sigKey = []byte("signature_hmac_secret_shared_key")
encKey = []byte("GCM_AES_256_secret_shared_key_32")
)
type fooClaims struct {
Foo string `json:"foo"`
}
/*
In this example you will learn the essentials
of the Iris builtin JWT middleware based on the github.com/kataras/jwt package.
*/
func main() {
app := iris.New()
signer := jwt.NewSigner(jwt.HS256, sigKey, 10*time.Minute)
// Enable payload encryption with:
// signer.WithGCM(encKey, nil)
app.Get("/", generateToken(signer))
verifier := jwt.NewVerifier(jwt.HS256, sigKey)
// Enable server-side token block feature (even before its expiration time):
verifier.WithDefaultBlocklist()
// Enable payload decryption with:
// verifier.WithGCM(encKey, nil)
verifyMiddleware := verifier.Verify(func() interface{} {
return new(fooClaims)
})
protectedAPI := app.Party("/protected")
// Register the verify middleware to allow access only to authorized clients.
protectedAPI.Use(verifyMiddleware)
// ^ or UseRouter(verifyMiddleware) to disallow unauthorized http error handlers too.
protectedAPI.Get("/", protected)
// Invalidate the token through server-side, even if it's not expired yet.
protectedAPI.Get("/logout", logout)
// http://localhost:8080
// http://localhost:8080/protected?token=$token (or Authorization: Bearer $token)
// http://localhost:8080/protected/logout?token=$token
// http://localhost:8080/protected?token=$token (401)
app.Listen(":8080")
}
func generateToken(signer *jwt.Signer) iris.Handler {
return func(ctx iris.Context) {
claims := fooClaims{Foo: "bar"}
token, err := signer.Sign(claims)
if err != nil {
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}
ctx.Write(token)
}
}
func protected(ctx iris.Context) {
// Get the verified and decoded claims.
claims := jwt.Get(ctx).(*fooClaims)
// Optionally, get token information if you want to work with them.
// Just an example on how you can retrieve all the standard claims (set by signer's max age, "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)
}
func logout(ctx iris.Context) {
err := ctx.Logout()
if err != nil {
ctx.WriteString(err.Error())
} else {
ctx.Writef("token invalidated, a new token is required to access the protected API")
}
}

View File

@@ -1,29 +0,0 @@
# Generate RSA
```sh
$ openssl genrsa -des3 -out private_rsa.pem 2048
```
```go
b, err := ioutil.ReadFile("./private_rsa.pem")
if err != nil {
panic(err)
}
key := jwt.MustParseRSAPrivateKey(b, []byte("pass"))
```
OR
```go
import "crypto/rand"
import "crypto/rsa"
key, err := rsa.GenerateKey(rand.Reader, 2048)
```
# Generate Ed25519
```sh
$ openssl genpkey -algorithm Ed25519 -out private_ed25519.pem
$ openssl req -x509 -key private_ed25519.pem -out cert_ed25519.pem -days 365
```

View File

@@ -1,368 +0,0 @@
// Package main shows how you can use the Iris unique JWT middleware.
// The file contains different kind of examples that all do the same job but,
// depending on your code style and your application's requirements, you may choose one over other.
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
// Claims a custom claims structure.
type Claims struct {
// Optionally define JWT's "iss" (Issuer),
// "sub" (Subject) and "aud" (Audience) for issuer and subject.
// The JWT's "exp" (expiration) and "iat" (issued at) are automatically
// set by the middleware.
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience []string `json:"aud"`
/*
Note that the above fields can be also extracted via:
jwt.GetTokenInfo(ctx).Claims
But in that example, we just showcase how these info can be embedded
inside your own Go structure.
*/
// Optionally define a "exp" (Expiry),
// unlike the rest, this is unset on creation
// (unless you want to override the middleware's max age option),
// it's filled automatically by the JWT middleware
// when the request token is verified.
// See the POST /user route.
Expiry *jwt.NumericDate `json:"exp"`
Username string `json:"username"`
}
func main() {
// Get keys from system's environment variables
// JWT_SECRET (for signing and verification) and JWT_SECRET_ENC(for encryption and decryption),
// or defaults to "secret" and "itsa16bytesecret" respectfully.
//
// Use the `jwt.New` instead for more flexibility, if necessary.
j := jwt.HMAC(15*time.Minute, "secret", "itsa16bytesecret")
/*
By default it extracts the token from url parameter "token={token}"
and the Authorization Bearer {token} header.
You can also take token from JSON body:
j.Extractors = append(j.Extractors, jwt.FromJSON)
*/
/* Optionally, enable block list to force-invalidate
verified tokens even before their expiration time.
This is useful when the client doesn't clear
the token on a user logout by itself.
The duration argument clears any expired token on each every tick.
There is a GC() method that can be manually called to clear expired blocked tokens
from the memory.
j.Blocklist = jwt.NewBlocklist(30*time.Minute)
OR NewBlocklistContext(stdContext, 30*time.Minute)
To invalidate a verified token just call:
j.Invalidate(ctx) inside a route handler.
*/
app := iris.New()
app.Logger().SetLevel("debug")
app.OnErrorCode(iris.StatusUnauthorized, func(ctx iris.Context) {
// Note that, any error stored by an authentication
// method in Iris is an iris.ErrPrivate.
// Available jwt errors:
// - ErrMissing
// - ErrMissingKey
// - ErrExpired
// - ErrNotValidYet
// - ErrIssuedInTheFuture
// - ErrBlocked
// An iris.ErrPrivate SHOULD never be displayed to the client as it is;
// because it may contain critical security information about the server.
//
// Also keep in mind that JWT middleware logs verification errors to the
// application's logger ("debug") so, normally you don't have to
// bother showing the verification error to the browser/client.
// However, you can retrieve that error and do what ever you feel right:
if err := ctx.GetErr(); err != nil {
// If we have an error stored,
// (JWT middleware stores any verification errors to the Context),
// set the error as response body,
// which is the default behavior if that
// wasn't an authentication error (as explained above)
ctx.WriteString(err.Error())
} else {
// Else, the default behavior when no error was occured;
// write the status text of the status code:
ctx.WriteString(iris.StatusText(iris.StatusUnauthorized))
}
})
app.Get("/authenticate", func(ctx iris.Context) {
claims := &Claims{
Issuer: "server",
Audience: []string{"user"},
Username: "kataras",
}
// WriteToken generates and sends the token to the client.
// To generate a token use: tok, err := j.Token(claims)
// then you can write it in any form you'd like.
// The expiration JWT fields are automatically
// set by the middleware, that means that your claims value
// only needs to fill fields that your application specifically requires.
j.WriteToken(ctx, claims)
})
// Middleware + type-safe method,
// useful in 99% of the cases, when your application
// requires token verification under a whole path prefix, e.g. /protected:
protectedAPI := app.Party("/protected")
{
protectedAPI.Use(j.Verify(func() interface{} {
// Must return a pointer to a type.
//
// The Iris JWT implementation is very sophisticated.
// We keep our claims in type-safe form.
// However, you are free to use raw Go maps
// (map[string]interface{} or iris.Map) too (example later on).
//
// Note that you can use the same "j" JWT instance
// to serve different types of claims on other group of routes,
// e.g. postRouter.Use(j.Verify(... return new(Post))).
return new(Claims)
}))
protectedAPI.Get("/", func(ctx iris.Context) {
claims := jwt.Get(ctx).(*Claims)
// All fields parsed from token are set to the claims,
// including the Expiry (if defined).
ctx.Writef("Username: %s\nExpires at: %s\nAudience: %s",
claims.Username, claims.Expiry.Time(), claims.Audience)
})
}
// Verify token inside a handler method,
// useful when you just need to verify a token on a single spot:
app.Get("/inline", func(ctx iris.Context) {
var claims Claims
_, err := j.VerifyToken(ctx, &claims)
if err != nil {
ctx.StopWithError(iris.StatusUnauthorized, err)
return
}
ctx.Writef("Username: %s\nExpires at: %s\n",
claims.Username, claims.Expiry.Time())
})
// Use a common map as claims method,
// not recommended, as we support typed claims but
// you can do it:
app.Get("/map/authenticate", func(ctx iris.Context) {
claims := map[string]interface{}{ // or iris.Map for shortcut.
"username": "kataras",
}
j.WriteToken(ctx, claims)
})
app.Get("/map/verify/middleware", j.Verify(func() interface{} {
return &iris.Map{} // or &map[string]interface{}{}
}), func(ctx iris.Context) {
claims := jwt.Get(ctx).(iris.Map)
// The Get method will unwrap the *iris.Map for you,
// so its values are directly accessible:
ctx.Writef("Username: %s\nExpires at: %s\n",
claims["username"], claims["exp"].(*jwt.NumericDate).Time())
})
app.Get("/map/verify", func(ctx iris.Context) {
claims := make(iris.Map) // or make(map[string]interface{})
tokenInfo, err := j.VerifyToken(ctx, &claims)
if err != nil {
ctx.StopWithError(iris.StatusUnauthorized, err)
return
}
ctx.Writef("Username: %s\nExpires at: %s\n",
claims["username"], tokenInfo.Claims.Expiry.Time()) /* the claims["exp"] is also set. */
})
// Use the new Context.User() to retrieve the verified client method:
// 1. Create a go stuct that implements the context.User interface:
app.Get("/users/authenticate", func(ctx iris.Context) {
user := &User{Username: "kataras"}
j.WriteToken(ctx, user)
})
usersAPI := app.Party("/users")
{
usersAPI.Use(j.Verify(func() interface{} {
return new(User)
}))
usersAPI.Get("/", func(ctx iris.Context) {
user := ctx.User()
userToken, _ := user.GetToken()
/*
You can also cast it to the underline implementation
and work with its fields:
expires := user.(*User).Expiry.Time()
*/
// OR use the GetTokenInfo to get the parsed token information:
expires := jwt.GetTokenInfo(ctx).Claims.Expiry.Time()
lifetime := expires.Sub(time.Now()) // remeaning time to be expired.
ctx.Writef("Username: %s\nAuthenticated at: %s\nLifetime: %s\nToken: %s\n",
user.GetUsername(), user.GetAuthorizedAt(), lifetime, userToken)
})
}
// http://localhost:8080/authenticate
// http://localhost:8080/protected?token={token}
// http://localhost:8080/inline?token={token}
//
// http://localhost:8080/map/authenticate
// http://localhost:8080/map/verify?token={token}
// http://localhost:8080/map/verify/middleware?token={token}
//
// http://localhost:8080/users/authenticate
// http://localhost:8080/users?token={token}
app.Listen(":8080")
}
// User is a custom implementation of the Iris Context User interface.
// Optionally, for JWT, you can also implement
// the SetToken(tok string) and
// Validate(ctx iris.Context, claims jwt.Claims, e jwt.Expected) error
// methods to set a token and add custom validation
// to a User value parsed from a token.
type User struct {
iris.User
Username string `json:"username"`
// Optionally, declare some JWT fields,
// they are automatically filled by the middleware itself.
IssuedAt *jwt.NumericDate `json:"iat"`
Expiry *jwt.NumericDate `json:"exp"`
Token string `json:"-"`
}
// GetUsername returns the Username.
// Look the iris/context.SimpleUser type
// for all the methods you can implement.
func (u *User) GetUsername() string {
return u.Username
}
// GetAuthorizedAt returns the IssuedAt time.
// This and the Get/SetToken methods showcase how you can map JWT standard fields
// to an Iris Context User.
func (u *User) GetAuthorizedAt() time.Time {
return u.IssuedAt.Time()
}
// GetToken is a User interface method.
func (u *User) GetToken() (string, error) {
return u.Token, nil
}
// SetToken is a special jwt.TokenSetter interface which is
// called automatically when a token is parsed to this User value.
func (u *User) SetToken(tok string) {
u.Token = tok
}
/*
func default_RSA_Example() {
j := jwt.RSA(15*time.Minute)
}
Same as:
func load_File_Or_Generate_RSA_Example() {
signKey, err := jwt.LoadRSA("jwt_sign.key", 2048)
if err != nil {
panic(err)
}
j, err := jwt.New(15*time.Minute, jwt.RS256, signKey)
if err != nil {
panic(err)
}
encKey, err := jwt.LoadRSA("jwt_enc.key", 2048)
if err != nil {
panic(err)
}
err = j.WithEncryption(jwt.A128CBCHS256, jwt.RSA15, encKey)
if err != nil {
panic(err)
}
}
*/
/*
func hmac_Example() {
// hmac
key := []byte("secret")
j, err := jwt.New(15*time.Minute, jwt.HS256, key)
if err != nil {
panic(err)
}
// OPTIONAL encryption:
encryptionKey := []byte("itsa16bytesecret")
err = j.WithEncryption(jwt.A128GCM, jwt.DIRECT, encryptionKey)
if err != nil {
panic(err)
}
}
*/
/*
func load_From_File_With_Password_Example() {
b, err := ioutil.ReadFile("./rsa_password_protected.key")
if err != nil {
panic(err)
}
signKey,err := jwt.ParseRSAPrivateKey(b, []byte("pass"))
if err != nil {
panic(err)
}
j, err := jwt.New(15*time.Minute, jwt.RS256, signKey)
if err != nil {
panic(err)
}
}
*/
/*
func generate_RSA_Example() {
signKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
panic(err)
}
encryptionKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
panic(err)
}
j, err := jwt.New(15*time.Minute, jwt.RS512, signKey)
if err != nil {
panic(err)
}
err = j.WithEncryption(jwt.A128CBCHS256, jwt.RSA15, encryptionKey)
if err != nil {
panic(err)
}
}
*/

View File

@@ -1,30 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,6B0BC214C94124FE
lAM48DEM/GdCDimr9Vhi+fSHLgduDb0l2BA4uhILgNby51jxY/4X3IqM6f3ImKX7
cEd9OBug+pwIugB0UW0L0f5Pd59Ovpiaz3xLci1/19ehYnMqsuP3YAnJm40hT5VP
p0gWRiR415PJ0fPeeJPFx5IsqvkTJ30LWZHUZX4EkdcL5L8PrVbmthGDbLh+OcMc
LzoP8eTglzlZF03nyvAol6+p2eZtvOJLu8nWG25q17kyBx6kEiCsWFcUBTX9G7sH
CM3naByDijqZXE/XXtmTMLSRRnlk7Q5WLxClroHlUP9y8BQFMo2TW4Z+vNjHUkc1
77ghabX1704bAlIE8LLZJKrm/C5+VKyV6117SVG/2bc4036Y5rStXpANbk1j4K0x
ADvpRhuTpifaogdvJP+8eXBdl841MQMRzWuZHp6UNYYQegoV9C+KHyJx4UPjZyzd
gblZmKgU+BsX3mV6MLhJtd6dheLZtpBsAlSstJxzmgwqz9faONYEGeItXO+NnxbA
mxAp/mI+Fz2jfgYlWjwkyPPzD4k/ZMMzB4XLkKKs9XaxUtTomiDkuYZfnABhxt73
xBy40V1rb/NyeW80pk1zEHM6Iy/48ETSp9U3k9sSOXjMhYbPXgxDtimV8w0qGFAo
2Tif7ZuaiuC38rOkoHK9C6vy2Dp8lQZ+QBnUKLeFsyhq9CaqSdnyUTMj3oEZXXf+
TqqeO+PTtl7JaNfGRq6/aMZqxACHkyVUvYvjZzx07CJ2fr+OtNqxallM6Oc/o9NZ
5u7lpgrYaKM/b67q0d2X/AoxR5zrZuM8eam3acD1PwHFQKbJWuFNmjWtnlZNuR3X
fZEmxIKwDlup8TxFcqbbZtPHuQA2mTMTqfRkf8oPSO+N6NNaUpb0ignYyA7Eu5GT
b02d/oNLETMikxUxntMSH7GhuOpfJyELz8krYTttbJ+a93h4wBeYW2+LyAr/cRLB
mbtKLtaN7f3FaOSnu8e0+zlJ7xglHPXqblRL9q6ZDM5UJtJD4rA7LPZHk/0Y1Kb6
hBh1qMDu0r3IV4X7MDacvxw7aa7D8TyXJiFSvxykVhds+ndjIe51Ics5908+lev3
nwE69PLMwyqe2vvE2oDwao4XJuBLCHjcv/VagRSz/XQGMbZqb3L6unyd3UPl8JjP
ovipNwM4rFnE54uiUUeki7TZGDYO72vQcSaLrmbeAWc2m202+rqLz0WMm6HpPmCv
IgexpX2MnIeHJ3+BlEjA2u+S6xNSD7qHGk2pb7DD8nRvUdSHAHeaQbrkEfEhhR2Q
Dw5gdw1JyQ0UKBl5ndn/1Ub2Asl016lZjpqHyMIVS4tFixACDsihEYMmq/zQmTj4
8oBZTU+fycN/KiGKZBsqxIwgYIeMz/GfvoyN5m57l6fwEZALVpveI1pP4fiZB/Z8
xLKa5JK6L10lAD1YHWc1dPhamf9Sb3JwN2CFtGvjOJ/YjAZu3jJoxi40DtRkE3Rh
HI8Cbx1OORzoo0kO0vy42rz5qunYyVmEzPKtOj+YjVEhVJ85yJZ9bTZtuyqMv8mH
cnwEeIFK8cmm9asbVzQGDwN/UGB4cO3LrMX1RYk4GRttTGlp0729BbmZmu00RnD/
-----END RSA PRIVATE KEY-----

View File

@@ -1,57 +1,65 @@
package main
import (
"fmt"
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
const (
accessTokenMaxAge = 10 * time.Minute
refreshTokenMaxAge = time.Hour
)
var (
privateKey, publicKey = jwt.MustLoadRSA("rsa_private_key.pem", "rsa_public_key.pem")
signer = jwt.NewSigner(jwt.RS256, privateKey, accessTokenMaxAge)
verifier = jwt.NewVerifier(jwt.RS256, publicKey)
)
// UserClaims a custom access claims structure.
type UserClaims struct {
// In order to separate refresh and access tokens on validation level:
// - Set a different Issuer, with a field of: Issuer string `json:"iss"`
// - Set the Iris JWT's json tag option "required" on an access token field,
// e.g. Username string `json:"username,required"`
// - Let the middleware validate the correct one based on the given MaxAge,
// which should be different between refresh and max age (refersh should be bigger)
// by setting the `jwt.ExpectRefreshToken` on Verify/VerifyToken/VerifyTokenString
// (see `refreshToken` function below)
ID string `json:"user_id"`
ID string `json:"user_id"`
// Do: `json:"username,required"` to have this field required
// or see the Validate method below instead.
Username string `json:"username"`
}
// Validate completes the middleware's custom ClaimsValidator.
// It will not accept a token which its claims missing the username field
// (useful to not accept refresh tokens generated by the same algorithm).
func (u *UserClaims) Validate() error {
if u.Username == "" {
return fmt.Errorf("username field is missing")
}
return nil
}
// For refresh token, we will just use the jwt.Claims
// structure which contains the standard JWT fields.
func main() {
app := iris.New()
app.OnErrorCode(iris.StatusUnauthorized, handleUnauthorized)
j := jwt.HMAC(15*time.Minute, "secret", "itsa16bytesecret")
app.Get("/authenticate", func(ctx iris.Context) {
generateTokenPair(ctx, j)
})
app.Get("/refresh", func(ctx iris.Context) {
refreshToken(ctx, j)
})
app.Get("/authenticate", generateTokenPair)
app.Get("/refresh", refreshToken)
protectedAPI := app.Party("/protected")
{
protectedAPI.Use(j.Verify(func() interface{} {
verifyMiddleware := verifier.Verify(func() interface{} {
return new(UserClaims)
})) // OR j.VerifyToken(ctx, &claims, jwt.MeetRequirements(&UserClaims{}))
})
protectedAPI.Use(verifyMiddleware)
protectedAPI.Get("/", func(ctx iris.Context) {
// Get token info, even if our UserClaims does not embed those
// through GetTokenInfo:
expiresAt := jwt.GetTokenInfo(ctx).Claims.Expiry.Time()
// Get your custom JWT claims through Get,
// which is a shortcut of GetTokenInfo(ctx).Value:
claims := jwt.Get(ctx).(*UserClaims)
ctx.Writef("Username: %s\nExpires at: %s\n", claims.Username, expiresAt)
ctx.Writef("Username: %s\n", claims.Username)
})
}
@@ -59,33 +67,33 @@ func main() {
// http://localhost:8080/authenticate (200) (response JSON {access_token, refresh_token})
// http://localhost:8080/protected?token={access_token} (200)
// http://localhost:8080/protected?token={refresh_token} (401)
// http://localhost:8080/refresh?token={refresh_token}
// http://localhost:8080/refresh?refresh_token={refresh_token}
// OR http://localhost:8080/refresh (request JSON{refresh_token = {refresh_token}}) (200) (response JSON {access_token, refresh_token})
// OR http://localhost:8080/refresh (request PLAIN TEXT of {refresh_token}) (200) (response JSON {access_token, refresh_token})
// http://localhost:8080/refresh?token={access_token} (401)
// http://localhost:8080/refresh?refresh_token={access_token} (401)
app.Listen(":8080")
}
func generateTokenPair(ctx iris.Context, j *jwt.JWT) {
func generateTokenPair(ctx iris.Context) {
// Simulate a user...
userID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
// Map the current user with the refresh token,
// so we make sure, on refresh route, that this refresh token owns
// to that user before re-generate.
refresh := jwt.Claims{Subject: userID}
refreshClaims := jwt.Claims{Subject: userID}
access := UserClaims{
accessClaims := UserClaims{
ID: userID,
Username: "kataras",
}
// Generates a Token Pair, long-live for refresh tokens, e.g. 1 hour.
// Second argument is the refresh claims and,
// the last one is the access token's claims.
tokenPair, err := j.TokenPair(1*time.Hour, refresh, access)
// First argument is the access claims,
// second argument is the refresh claims,
// third argument is the refresh max age.
tokenPair, err := signer.NewTokenPair(accessClaims, refreshClaims, refreshTokenMaxAge)
if err != nil {
ctx.Application().Logger().Debugf("token pair: %v", err)
ctx.Application().Logger().Errorf("token pair: %v", err)
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}
@@ -95,14 +103,12 @@ func generateTokenPair(ctx iris.Context, j *jwt.JWT) {
ctx.JSON(tokenPair)
}
func refreshToken(ctx iris.Context, j *jwt.JWT) {
/*
We could pass a jwt.Claims pointer as the second argument,
but we don't have to because the method already returns
the standard JWT claims information back to us:
refresh, err := VerifyRefreshToken(ctx, nil)
*/
// There are various methods of refresh token, depending on the application requirements.
// In this example we will accept a refresh token only, we will verify only a refresh token
// and we re-generate a whole new pair. An alternative would be to accept a token pair
// of both access and refresh tokens, verify the refresh, verify the access with a Leeway time
// and check if its going to expire soon, then generate a single access token.
func refreshToken(ctx iris.Context) {
// Assuming you have access to the current user, e.g. sessions.
//
// Simulate a database call against our jwt subject
@@ -110,23 +116,46 @@ func refreshToken(ctx iris.Context, j *jwt.JWT) {
// * Note: You can remove the ExpectSubject and do this validation later on by yourself.
currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
// Get the refresh token from ?refresh_token=$token OR
// the request body's JSON{"refresh_token": "$token"}.
refreshToken := []byte(ctx.URLParam("refresh_token"))
if len(refreshToken) == 0 {
// You can read the whole body with ctx.GetBody/ReadBody too.
var tokenPair jwt.TokenPair
if err := ctx.ReadJSON(&tokenPair); err != nil {
ctx.StopWithError(iris.StatusBadRequest, err)
return
}
refreshToken = tokenPair.RefreshToken
}
// Verify the refresh token, which its subject MUST match the "currentUserID".
_, err := j.VerifyRefreshToken(ctx, nil, jwt.ExpectSubject(currentUserID))
_, err := verifier.VerifyToken(refreshToken, jwt.Expected{Subject: currentUserID})
if err != nil {
ctx.Application().Logger().Debugf("verify refresh token: %v", err)
ctx.Application().Logger().Errorf("verify refresh token: %v", err)
ctx.StatusCode(iris.StatusUnauthorized)
return
}
/* Custom validation checks can be performed after Verify calls too:
currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
userID := refresh.Claims.Subject
userID := verifiedToken.StandardClaims.Subject
if userID != currentUserID {
ctx.StopWithStatus(iris.StatusUnauthorized)
return
}
*/
// All OK, re-generate the new pair and send to client.
generateTokenPair(ctx, j)
// All OK, re-generate the new pair and send to client,
// we could only generate an access token as well.
generateTokenPair(ctx)
}
func handleUnauthorized(ctx iris.Context) {
if err := ctx.GetErr(); err != nil {
ctx.Application().Logger().Errorf("unauthorized: %v", err)
}
ctx.WriteString("Unauthorized")
}

View File

@@ -0,0 +1,27 @@
-----BEGIN PRIVATE KEY-----
MIIEowIBAAKCAQEArwO0q8WbBvrplz3lTQjsWu66HC7M3mVAjmjLq8Wj/ipqVtiJ
MrUL9t/0q9PNO/KX9u+HayFNYM4TnYkXVZX3M5E31W8fPPy74D/XpqFwrwT7bAEw
pT51JJyxkoBAyOh08lmR2EYvpGF7qErra7qbkk4LGFbhoFCXdMLXguT4rPymkzFH
dQrmGYOBS+v9imSuJddCZpXyv6Ko7AKB4mhzg4RC5RJZO5GEHVUrSMHxZB0syF8c
U+28iL8A7SlGKTNZPZiHmCQVRqA6WlllL/YV/t6p24kaNZBUp9JGbAzOeKuVUv2u
vfNKwB/aBwnFKauM9I6RmC4bnI1nGHjETlNNWwIDAQABAoIBAHBPKHmybTGlgpET
nzo4J7SSzcuYHM/6mdrJVSn9wqcwAN2KR0DK/cqHHTPGz0VRAEPuojAVRtqAZAYM
G3VIr0HgRrwoextf9BCL549+uhkWUWGVwenIktPT2f/xXaGPyrxazkTDhX8vL3Nn
4HtZXMweWPBdkJyYGxlKj5Hn7czTpG3VKpvpHeFlY4caF+FT2as1jcQ1MjPnGslH
Ss+sYPBp/70w2T114Z4wlR4OryI1LeuFeje9obrn0HAmJd0ZKYM21awp/YWJ/y8J
wIH6XQ4AGR9iTRhuffK1XRM/Iec3K/YhOn4PtKdT7OsIujAKY7A9WcqSFif+/E1g
jom3eMECgYEAw5Zdqt2uZ19FuDlDTW4Kw8Z2NyXgWp33LkAXG1mJw7bqDhfPeB1c
xTPs4i4RubGuDusygxZ3GgJAO7tLGzNQfWNoi03mM7Q/BJGkA9VZr+U28zsSRQOQ
+J9xNsdgUMP1js7X/NNM2bxTC8zy9wEsWr9JwNo1C7uHTE9WXAumBI8CgYEA5RKV
niSbyko36W3Vi0ZnGBrRhy0Eiq85V2mhWzHN+txcv+8aISow2wioTUzrpR0aVZ4j
v9+siJENlALVzdUFihy0lPxHqLJT746Cixz95WRTLkdHeNllV0DMfOph2x3j1Hjd
3PgTv+jqb6npY0/2Vb2pp4t/zVikGaObsAalSHUCgYBne8B1bjMfqI3n6gxNBIMX
kILtrNGmwFuPEgPnyZkVf0sZR8nSwJ5cDJwyE7P3LyZr6E9igllj3nsD35Xef2j/
3r/qrL2275BEJ5bDHHgGk91eFgwVjcx/b0TkedrhAL2E4LXwpA/OSFEcNkT7IZjJ
Ltqj+hAE9CSi4HtN2i/tywKBgBotKn28zzSpkIQTMgDNVcCSZ/kbctZqOZI8lty1
70TIY6znJMQ/bv/ImHrk3FSs47J+9LTbWXrtoHCWdlokCpMCvrv7rDCh2Cea0F4X
PQg2k67JJGix5vu2guePXQlN/Bfui+PRUWhvtEJ4VxwrKgoYN0fXEA6mH3JymLrf
t4l1AoGBALk4o9swGjw7MnByYJmOidlJ0p9Wj1BWWJJYoYX2VfjIuvZj6BNxkEb0
aVmYRC+40e9L1rOyrlyaO/TiQaIPE4ljVs/AmMKGz8sIcVfwdyERH3nDrXxvlAav
lSvfKoYM3J+5c63CDuU45gztpmavNerzCczqYTLOEMx1eCLHOQlx
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwO0q8WbBvrplz3lTQjs
Wu66HC7M3mVAjmjLq8Wj/ipqVtiJMrUL9t/0q9PNO/KX9u+HayFNYM4TnYkXVZX3
M5E31W8fPPy74D/XpqFwrwT7bAEwpT51JJyxkoBAyOh08lmR2EYvpGF7qErra7qb
kk4LGFbhoFCXdMLXguT4rPymkzFHdQrmGYOBS+v9imSuJddCZpXyv6Ko7AKB4mhz
g4RC5RJZO5GEHVUrSMHxZB0syF8cU+28iL8A7SlGKTNZPZiHmCQVRqA6WlllL/YV
/t6p24kaNZBUp9JGbAzOeKuVUv2uvfNKwB/aBwnFKauM9I6RmC4bnI1nGHjETlNN
WwIDAQAB
-----END PUBLIC KEY-----