iris/_examples/auth/jwt/tutorial/api/auth.go

143 lines
3.3 KiB
Go
Raw Normal View History

2020-11-04 20:12:13 +01:00
package api
import (
"fmt"
"os"
"time"
"myapp/domain/model"
"myapp/domain/repository"
"myapp/util"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
const defaultSecretKey = "sercrethatmaycontainch@r$32chars"
func getSecretKey() string {
secret := os.Getenv(util.AppName + "_SECRET")
if secret == "" {
return defaultSecretKey
}
return secret
}
// UserClaims represents the user token claims.
type UserClaims struct {
UserID string `json:"user_id"`
Roles []model.Role `json:"roles"`
}
// Validate implements the custom struct claims validator,
// this is totally optionally and maybe unnecessary but good to know how.
func (u *UserClaims) Validate() error {
if u.UserID == "" {
return fmt.Errorf("%w: %s", jwt.ErrMissingKey, "user_id")
}
return nil
}
// Verify allows only authorized clients.
func Verify() iris.Handler {
secret := getSecretKey()
verifier := jwt.NewVerifier(jwt.HS256, []byte(secret), jwt.Expected{Issuer: util.AppName})
verifier.Extractors = []jwt.TokenExtractor{jwt.FromHeader} // extract token only from Authorization: Bearer $token
return verifier.Verify(func() interface{} {
return new(UserClaims)
})
}
// AllowAdmin allows only authorized clients with "admin" access role.
// Should be registered after Verify.
func AllowAdmin(ctx iris.Context) {
if !IsAdmin(ctx) {
ctx.StopWithText(iris.StatusForbidden, "admin access required")
return
}
ctx.Next()
}
// SignIn accepts the user form data and returns a token to authorize a client.
func SignIn(repo repository.UserRepository) iris.Handler {
secret := getSecretKey()
signer := jwt.NewSigner(jwt.HS256, []byte(secret), 15*time.Minute)
return func(ctx iris.Context) {
/*
type LoginForm struct {
Username string `form:"username"`
Password string `form:"password"`
}
and ctx.ReadForm OR use the ctx.FormValue(s) method.
*/
var (
username = ctx.FormValue("username")
password = ctx.FormValue("password")
)
user, ok := repo.GetByUsernameAndPassword(username, password)
if !ok {
ctx.StopWithText(iris.StatusBadRequest, "wrong username or password")
return
}
claims := UserClaims{
UserID: user.ID,
Roles: user.Roles,
}
// Optionally, generate a JWT ID.
jti, err := util.GenerateUUID()
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
token, err := signer.Sign(claims, jwt.Claims{
ID: jti,
Issuer: util.AppName,
})
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.Write(token)
}
}
// SignOut invalidates a user from server-side using the jwt Blocklist.
// It's not used as we don't attach a blocklist, the user can be logged out from our client
// and we don't use access token so we don't actually need this in this example.
func SignOut(ctx iris.Context) {
ctx.Logout()
}
// GetClaims returns the current authorized client claims.
func GetClaims(ctx iris.Context) *UserClaims {
claims := jwt.Get(ctx).(*UserClaims)
return claims
}
// GetUserID returns the current authorized client's user id extracted from claims.
func GetUserID(ctx iris.Context) string {
return GetClaims(ctx).UserID
}
// IsAdmin reports whether the current client has admin access.
func IsAdmin(ctx iris.Context) bool {
for _, role := range GetClaims(ctx).Roles {
if role == model.Admin {
return true
}
}
return false
}