2020-09-16 12:57:11 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/kataras/iris/v12"
|
|
|
|
"github.com/kataras/iris/v12/middleware/jwt"
|
|
|
|
)
|
|
|
|
|
|
|
|
// UserClaims a custom claims structure. You can just use jwt.Claims too.
|
|
|
|
type UserClaims struct {
|
|
|
|
jwt.Claims
|
|
|
|
Username string
|
|
|
|
}
|
|
|
|
|
|
|
|
// TokenPair holds the access token and refresh token response.
|
|
|
|
type TokenPair struct {
|
|
|
|
AccessToken string `json:"access_token"`
|
|
|
|
RefreshToken string `json:"refresh_token"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
app := iris.New()
|
|
|
|
|
|
|
|
// Access token, short-live.
|
|
|
|
accessJWT := jwt.HMAC(15*time.Minute, "secret", "itsa16bytesecret")
|
|
|
|
// Refresh token, long-live. Important: Give different secret keys(!)
|
|
|
|
refreshJWT := jwt.HMAC(1*time.Hour, "other secret", "other16bytesecre")
|
|
|
|
// On refresh token, we extract it only from a request body
|
|
|
|
// of JSON, e.g. {"refresh_token": $token }.
|
|
|
|
// You can also do it manually in the handler level though.
|
|
|
|
refreshJWT.Extractors = []jwt.TokenExtractor{
|
|
|
|
jwt.FromJSON("refresh_token"),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate access and refresh tokens and send to the client.
|
|
|
|
app.Get("/authenticate", func(ctx iris.Context) {
|
|
|
|
tokenPair, err := generateTokenPair(accessJWT, refreshJWT)
|
|
|
|
if err != nil {
|
|
|
|
ctx.StopWithStatus(iris.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.JSON(tokenPair)
|
|
|
|
})
|
|
|
|
|
|
|
|
app.Get("/refresh", func(ctx iris.Context) {
|
|
|
|
// Manual (if jwt.FromJSON missing):
|
|
|
|
// var payload = struct {
|
|
|
|
// RefreshToken string `json:"refresh_token"`
|
|
|
|
// }{}
|
|
|
|
//
|
|
|
|
// err := ctx.ReadJSON(&payload)
|
|
|
|
// if err != nil {
|
|
|
|
// ctx.StatusCode(iris.StatusBadRequest)
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// j.VerifyTokenString(ctx, payload.RefreshToken, &claims)
|
|
|
|
|
|
|
|
var claims jwt.Claims
|
|
|
|
if err := refreshJWT.VerifyToken(ctx, &claims); err != nil {
|
|
|
|
ctx.Application().Logger().Warnf("verify refresh token: %v", err)
|
|
|
|
ctx.StopWithStatus(iris.StatusUnauthorized)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
userID := claims.Subject
|
|
|
|
if userID == "" {
|
|
|
|
ctx.StopWithStatus(iris.StatusUnauthorized)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Simulate a database call against our jwt subject.
|
|
|
|
if userID != "53afcf05-38a3-43c3-82af-8bbbe0e4a149" {
|
|
|
|
ctx.StopWithStatus(iris.StatusUnauthorized)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// All OK, re-generate the new pair and send to client.
|
|
|
|
tokenPair, err := generateTokenPair(accessJWT, refreshJWT)
|
|
|
|
if err != nil {
|
|
|
|
ctx.StopWithStatus(iris.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.JSON(tokenPair)
|
|
|
|
})
|
|
|
|
|
|
|
|
app.Get("/", func(ctx iris.Context) {
|
|
|
|
var claims UserClaims
|
|
|
|
if err := accessJWT.VerifyToken(ctx, &claims); err != nil {
|
|
|
|
ctx.StopWithStatus(iris.StatusUnauthorized)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Writef("Username: %s\nExpires at: %s\n", claims.Username, claims.Expiry.Time())
|
|
|
|
})
|
|
|
|
|
|
|
|
// http://localhost:8080 (401)
|
|
|
|
// http://localhost:8080/authenticate (200) (response JSON {access_token, refresh_token})
|
|
|
|
// http://localhost:8080?token={access_token} (200)
|
|
|
|
// http://localhost:8080?token={refresh_token} (401)
|
|
|
|
// http://localhost:8080/refresh (request JSON{refresh_token = {refresh_token}}) (200) (response JSON {access_token, refresh_token})
|
|
|
|
app.Listen(":8080")
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateTokenPair(accessJWT, refreshJWT *jwt.JWT) (TokenPair, error) {
|
|
|
|
standardClaims := jwt.Claims{Issuer: "an-issuer", Audience: jwt.Audience{"an-audience"}}
|
|
|
|
|
|
|
|
customClaims := UserClaims{
|
|
|
|
Claims: accessJWT.Expiry(standardClaims),
|
|
|
|
Username: "kataras",
|
|
|
|
}
|
|
|
|
|
|
|
|
accessToken, err := accessJWT.Token(customClaims)
|
|
|
|
if err != nil {
|
|
|
|
return TokenPair{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// At refresh tokens you don't need any custom claims.
|
|
|
|
refreshClaims := refreshJWT.Expiry(jwt.Claims{
|
|
|
|
ID: "refresh_kataras",
|
|
|
|
// For example, the User ID,
|
2020-09-29 18:19:19 +02:00
|
|
|
// this is necessary to check against the database
|
2020-09-16 12:57:11 +02:00
|
|
|
// if the user still exist or has credentials to access our page.
|
|
|
|
Subject: "53afcf05-38a3-43c3-82af-8bbbe0e4a149",
|
|
|
|
})
|
|
|
|
|
|
|
|
refreshToken, err := refreshJWT.Token(refreshClaims)
|
|
|
|
if err != nil {
|
|
|
|
return TokenPair{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return TokenPair{
|
|
|
|
AccessToken: accessToken,
|
|
|
|
RefreshToken: refreshToken,
|
|
|
|
}, nil
|
|
|
|
}
|