iris/_examples/auth/jwt/refresh-token/main.go

133 lines
4.5 KiB
Go
Raw Normal View History

package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
// 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"`
Username string `json:"username"`
}
// For refresh token, we will just use the jwt.Claims
// structure which contains the standard JWT fields.
func main() {
app := iris.New()
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)
})
protectedAPI := app.Party("/protected")
{
protectedAPI.Use(j.Verify(func() interface{} {
return new(UserClaims)
})) // OR j.VerifyToken(ctx, &claims, jwt.MeetRequirements(&UserClaims{}))
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)
})
}
// http://localhost:8080/protected (401)
// 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}
// 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)
app.Listen(":8080")
}
func generateTokenPair(ctx iris.Context, j *jwt.JWT) {
// 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}
access := 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)
if err != nil {
ctx.Application().Logger().Debugf("token pair: %v", err)
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}
// Send the generated token pair to the client.
// The tokenPair looks like: {"access_token": $token, "refresh_token": $token}
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)
*/
// Assuming you have access to the current user, e.g. sessions.
//
// Simulate a database call against our jwt subject
// to make sure that this refresh token is a pair generated by this user.
// * Note: You can remove the ExpectSubject and do this validation later on by yourself.
currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
// Verify the refresh token, which its subject MUST match the "currentUserID".
_, err := j.VerifyRefreshToken(ctx, nil, jwt.ExpectSubject(currentUserID))
if err != nil {
ctx.Application().Logger().Debugf("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
if userID != currentUserID {
ctx.StopWithStatus(iris.StatusUnauthorized)
return
}
*/
// All OK, re-generate the new pair and send to client.
generateTokenPair(ctx, j)
}