mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
141 lines
4.6 KiB
Go
141 lines
4.6 KiB
Go
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 (request JSON{refresh_token = {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) {
|
|
var tokenPair jwt.TokenPair
|
|
|
|
if token := ctx.URLParam("token"); token != "" {
|
|
// Grab the refresh token from the url argument.
|
|
tokenPair.RefreshToken = token
|
|
} else {
|
|
// Otherwise grab the refresh token from a JSON body (you can let it fetch by URL parameter too but
|
|
// it's common practice that you read it from a json body as
|
|
// it may contain the access token too (the same response we sent on generateTokenPair)).
|
|
err := ctx.ReadJSON(&tokenPair)
|
|
if err != nil {
|
|
ctx.StatusCode(iris.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
|
|
var refreshClaims jwt.Claims
|
|
_, err := j.VerifyTokenString(ctx, tokenPair.RefreshToken, &refreshClaims, jwt.ExpectRefreshToken)
|
|
if err != nil {
|
|
ctx.Application().Logger().Debugf("verify refresh token: %v", err)
|
|
ctx.StatusCode(iris.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// 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.
|
|
currentUserID := "53afcf05-38a3-43c3-82af-8bbbe0e4a149"
|
|
|
|
userID := refreshClaims.Subject
|
|
if userID != currentUserID {
|
|
ctx.StopWithStatus(iris.StatusUnauthorized)
|
|
return
|
|
}
|
|
//
|
|
// Otherwise, the request must contain the (old) access token too,
|
|
// even if it's invalid, we can still fetch its fields, such as the user id.
|
|
// [...leave it for you]
|
|
|
|
// All OK, re-generate the new pair and send to client.
|
|
generateTokenPair(ctx, j)
|
|
}
|