2020-05-27 11:02:17 +02:00
|
|
|
package jwt
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2020-05-31 16:57:30 +02:00
|
|
|
"os"
|
2020-05-27 11:02:17 +02:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/kataras/iris/v12/context"
|
|
|
|
|
|
|
|
"github.com/square/go-jose/v3"
|
|
|
|
"github.com/square/go-jose/v3/jwt"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
context.SetHandlerName("iris/middleware/jwt.*", "iris.jwt")
|
|
|
|
}
|
|
|
|
|
|
|
|
// TokenExtractor is a function that takes a context as input and returns
|
|
|
|
// a token. An empty string should be returned if no token found
|
|
|
|
// without additional information.
|
|
|
|
type TokenExtractor func(context.Context) string
|
|
|
|
|
|
|
|
// FromHeader is a token extractor.
|
|
|
|
// It reads the token from the Authorization request header of form:
|
|
|
|
// Authorization: "Bearer {token}".
|
|
|
|
func FromHeader(ctx context.Context) string {
|
|
|
|
authHeader := ctx.GetHeader("Authorization")
|
|
|
|
if authHeader == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// pure check: authorization header format must be Bearer {token}
|
|
|
|
authHeaderParts := strings.Split(authHeader, " ")
|
|
|
|
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return authHeaderParts[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromQuery is a token extractor.
|
|
|
|
// It reads the token from the "token" url query parameter.
|
|
|
|
func FromQuery(ctx context.Context) string {
|
|
|
|
return ctx.URLParam("token")
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromJSON is a token extractor.
|
|
|
|
// Reads a json request body and extracts the json based on the given field.
|
|
|
|
// The request content-type should contain the: application/json header value, otherwise
|
|
|
|
// this method will not try to read and consume the body.
|
|
|
|
func FromJSON(jsonKey string) TokenExtractor {
|
|
|
|
return func(ctx context.Context) string {
|
|
|
|
if ctx.GetContentTypeRequested() != context.ContentJSONHeaderValue {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
var m context.Map
|
|
|
|
if err := ctx.ReadJSON(&m); err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if m == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
v, ok := m[jsonKey]
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
tok, ok := v.(string)
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return tok
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// JWT holds the necessary information the middleware need
|
|
|
|
// to sign and verify tokens.
|
|
|
|
//
|
|
|
|
// The `RSA(privateFile, publicFile, password)` package-level helper function
|
|
|
|
// can be used to decode the SignKey and VerifyKey.
|
|
|
|
type JWT struct {
|
|
|
|
// MaxAge is the expiration duration of the generated tokens.
|
|
|
|
MaxAge time.Duration
|
|
|
|
|
|
|
|
// Extractors are used to extract a raw token string value
|
|
|
|
// from the request.
|
|
|
|
// Builtin extractors:
|
|
|
|
// * FromHeader
|
|
|
|
// * FromQuery
|
|
|
|
// * FromJSON
|
|
|
|
// Defaults to a slice of `FromHeader` and `FromQuery`.
|
|
|
|
Extractors []TokenExtractor
|
|
|
|
|
|
|
|
// Signer is used to sign the token.
|
|
|
|
// It is set on `New` and `Default` package-level functions.
|
|
|
|
Signer jose.Signer
|
|
|
|
// VerificationKey is used to verify the token (public key).
|
|
|
|
VerificationKey interface{}
|
|
|
|
|
|
|
|
// Encrypter is used to, optionally, encrypt the token.
|
2020-05-28 15:20:58 +02:00
|
|
|
// It is set on `WithEncryption` method.
|
2020-05-27 11:02:17 +02:00
|
|
|
Encrypter jose.Encrypter
|
|
|
|
// DecriptionKey is used to decrypt the token (private key)
|
|
|
|
DecriptionKey interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type privateKey interface{ Public() crypto.PublicKey }
|
|
|
|
|
|
|
|
// New returns a new JWT instance.
|
|
|
|
// It accepts a maximum time duration for token expiration
|
|
|
|
// and the algorithm among with its key for signing and verification.
|
|
|
|
//
|
|
|
|
// See `WithEncryption` method to add token encryption too.
|
|
|
|
// Use `Token` method to generate a new token string
|
|
|
|
// and `VerifyToken` method to decrypt, verify and bind claims of an incoming request token.
|
|
|
|
// Token, by default, is extracted by "Authorization: Bearer {token}" request header and
|
|
|
|
// url query parameter of "token". Token extractors can be modified through the `Extractors` field.
|
|
|
|
//
|
|
|
|
// For example, if you want to sign and verify using RSA-256 key:
|
|
|
|
// 1. Generate key file, e.g:
|
|
|
|
// $ openssl genrsa -des3 -out private.pem 2048
|
|
|
|
// 2. Read file contents with io.ReadFile("./private.pem")
|
2020-05-31 16:57:30 +02:00
|
|
|
// 3. Pass the []byte result to the `ParseRSAPrivateKey(contents, password)` package-level helper
|
2020-05-27 11:02:17 +02:00
|
|
|
// 4. Use the result *rsa.PrivateKey as "key" input parameter of this `New` function.
|
|
|
|
//
|
|
|
|
// See aliases.go file for available algorithms.
|
|
|
|
func New(maxAge time.Duration, alg SignatureAlgorithm, key interface{}) (*JWT, error) {
|
|
|
|
sig, err := jose.NewSigner(jose.SigningKey{
|
|
|
|
Algorithm: alg,
|
|
|
|
Key: key,
|
|
|
|
}, (&jose.SignerOptions{}).WithType("JWT"))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
j := &JWT{
|
|
|
|
Signer: sig,
|
|
|
|
VerificationKey: key,
|
|
|
|
MaxAge: maxAge,
|
|
|
|
Extractors: []TokenExtractor{FromHeader, FromQuery},
|
|
|
|
}
|
|
|
|
|
|
|
|
if s, ok := key.(privateKey); ok {
|
|
|
|
j.VerificationKey = s.Public()
|
|
|
|
}
|
|
|
|
|
|
|
|
return j, nil
|
|
|
|
}
|
|
|
|
|
2020-06-07 14:26:06 +02:00
|
|
|
// Default key filenames for `RSA`.
|
2020-05-31 16:57:30 +02:00
|
|
|
const (
|
2020-06-07 14:26:06 +02:00
|
|
|
DefaultSignFilename = "jwt_sign.key"
|
|
|
|
DefaultEncFilename = "jwt_enc.key"
|
2020-05-31 16:57:30 +02:00
|
|
|
)
|
|
|
|
|
2020-06-07 14:26:06 +02:00
|
|
|
// RSA returns a new `JWT` instance.
|
|
|
|
// It tries to parse RSA256 keys from "filenames[0]" (defaults to "jwt_sign.key") and
|
|
|
|
// "filenames[1]" (defaults to "jwt_enc.key") files or generates and exports new random keys.
|
2020-05-31 16:57:30 +02:00
|
|
|
//
|
|
|
|
// It panics on errors.
|
|
|
|
// Use the `New` package-level function instead for more options.
|
2020-06-07 14:26:06 +02:00
|
|
|
func RSA(maxAge time.Duration, filenames ...string) *JWT {
|
|
|
|
var (
|
|
|
|
signFilename = DefaultSignFilename
|
|
|
|
encFilename = DefaultEncFilename
|
|
|
|
)
|
|
|
|
|
|
|
|
switch len(filenames) {
|
|
|
|
case 1:
|
|
|
|
signFilename = filenames[0]
|
|
|
|
case 2:
|
|
|
|
encFilename = filenames[1]
|
|
|
|
}
|
|
|
|
|
2020-05-31 16:57:30 +02:00
|
|
|
// Do not try to create or load enc key if only sign key already exists.
|
|
|
|
withEncryption := true
|
2020-06-07 14:26:06 +02:00
|
|
|
if fileExists(signFilename) {
|
|
|
|
withEncryption = fileExists(encFilename)
|
2020-05-31 16:57:30 +02:00
|
|
|
}
|
|
|
|
|
2020-06-07 14:26:06 +02:00
|
|
|
sigKey, err := LoadRSA(signFilename, 2048)
|
2020-05-31 16:57:30 +02:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
j, err := New(maxAge, RS256, sigKey)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if withEncryption {
|
2020-06-07 14:26:06 +02:00
|
|
|
encKey, err := LoadRSA(encFilename, 2048)
|
2020-05-31 16:57:30 +02:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
err = j.WithEncryption(A128CBCHS256, RSA15, encKey)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return j
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
signEnv = "JWT_SECRET"
|
|
|
|
encEnv = "JWT_SECRET_ENC"
|
|
|
|
)
|
|
|
|
|
|
|
|
func getenv(key string, def string) string {
|
|
|
|
v := os.Getenv(key)
|
|
|
|
if v == "" {
|
|
|
|
return def
|
|
|
|
}
|
|
|
|
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2020-06-07 14:26:06 +02:00
|
|
|
// HMAC returns a new `JWT` instance.
|
2020-05-31 16:57:30 +02:00
|
|
|
// It tries to read hmac256 secret keys from system environment variables:
|
|
|
|
// * JWT_SECRET for signing and verification key and
|
|
|
|
// * JWT_SECRET_ENC for encryption and decryption key
|
|
|
|
// and defaults them to the given "keys" respectfully.
|
|
|
|
//
|
|
|
|
// It panics on errors.
|
|
|
|
// Use the `New` package-level function instead for more options.
|
2020-06-07 14:26:06 +02:00
|
|
|
func HMAC(maxAge time.Duration, keys ...string) *JWT {
|
2020-05-31 16:57:30 +02:00
|
|
|
var defaultSignSecret, defaultEncSecret string
|
|
|
|
|
|
|
|
switch len(keys) {
|
|
|
|
case 1:
|
|
|
|
defaultSignSecret = keys[0]
|
|
|
|
case 2:
|
|
|
|
defaultEncSecret = keys[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
signSecret := getenv(signEnv, defaultSignSecret)
|
|
|
|
encSecret := getenv(encEnv, defaultEncSecret)
|
|
|
|
|
|
|
|
j, err := New(maxAge, HS256, []byte(signSecret))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if encSecret != "" {
|
|
|
|
err = j.WithEncryption(A128GCM, DIRECT, []byte(encSecret))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return j
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:02:17 +02:00
|
|
|
// WithEncryption method enables encryption and decryption of the token.
|
|
|
|
// It sets an appropriate encrypter(`Encrypter` and the `DecriptionKey` fields) based on the key type.
|
|
|
|
func (j *JWT) WithEncryption(contentEncryption ContentEncryption, alg KeyAlgorithm, key interface{}) error {
|
|
|
|
var publicKey interface{} = key
|
|
|
|
if s, ok := key.(privateKey); ok {
|
|
|
|
publicKey = s.Public()
|
|
|
|
}
|
|
|
|
|
|
|
|
enc, err := jose.NewEncrypter(contentEncryption, jose.Recipient{
|
|
|
|
Algorithm: alg,
|
|
|
|
Key: publicKey,
|
|
|
|
},
|
|
|
|
(&jose.EncrypterOptions{}).WithType("JWT").WithContentType("JWT"),
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
j.Encrypter = enc
|
|
|
|
j.DecriptionKey = key
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expiry returns a new standard Claims with
|
|
|
|
// the `Expiry` and `IssuedAt` fields of the "claims" filled
|
|
|
|
// based on the given "maxAge" duration.
|
|
|
|
//
|
|
|
|
// See the `JWT.Expiry` method too.
|
|
|
|
func Expiry(maxAge time.Duration, claims Claims) Claims {
|
|
|
|
now := time.Now()
|
2020-06-07 14:26:06 +02:00
|
|
|
claims.Expiry = NewNumericDate(now.Add(maxAge))
|
|
|
|
claims.IssuedAt = NewNumericDate(now)
|
2020-05-27 11:02:17 +02:00
|
|
|
return claims
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expiry method same as `Expiry` package-level function,
|
|
|
|
// it returns a Claims with the expiration fields of the "claims"
|
|
|
|
// filled based on the JWT's `MaxAge` field.
|
|
|
|
// Only use it when this standard "claims"
|
|
|
|
// is embedded on a custom claims structure.
|
|
|
|
// Usage:
|
|
|
|
// type UserClaims struct {
|
|
|
|
// jwt.Claims
|
|
|
|
// Username string
|
|
|
|
// }
|
|
|
|
// [...]
|
|
|
|
// standardClaims := j.Expiry(jwt.Claims{...})
|
|
|
|
// customClaims := UserClaims{
|
|
|
|
// Claims: standardClaims,
|
|
|
|
// Username: "kataras",
|
|
|
|
// }
|
|
|
|
// j.WriteToken(ctx, customClaims)
|
|
|
|
func (j *JWT) Expiry(claims Claims) Claims {
|
|
|
|
return Expiry(j.MaxAge, claims)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Token generates and returns a new token string.
|
|
|
|
// See `VerifyToken` too.
|
|
|
|
func (j *JWT) Token(claims interface{}) (string, error) {
|
2020-06-07 14:26:06 +02:00
|
|
|
// switch c := claims.(type) {
|
|
|
|
// case Claims:
|
|
|
|
// claims = Expiry(j.MaxAge, c)
|
|
|
|
// case map[string]interface{}: let's not support map.
|
|
|
|
// now := time.Now()
|
|
|
|
// c["iat"] = now.Unix()
|
|
|
|
// c["exp"] = now.Add(j.MaxAge).Unix()
|
|
|
|
// }
|
2020-05-27 11:02:17 +02:00
|
|
|
if c, ok := claims.(Claims); ok {
|
|
|
|
claims = Expiry(j.MaxAge, c)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
token string
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
// jwt.Builder and jwt.NestedBuilder contain same methods but they are not the same.
|
|
|
|
if j.DecriptionKey != nil {
|
|
|
|
token, err = jwt.SignedAndEncrypted(j.Signer, j.Encrypter).Claims(claims).CompactSerialize()
|
|
|
|
} else {
|
|
|
|
token, err = jwt.Signed(j.Signer).Claims(claims).CompactSerialize()
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
2020-06-07 14:26:06 +02:00
|
|
|
/* Let's no support maps, typed claim is the way to go.
|
|
|
|
// validateMapClaims validates claims of map type.
|
|
|
|
func validateMapClaims(m map[string]interface{}, e jwt.Expected, leeway time.Duration) error {
|
|
|
|
if !e.Time.IsZero() {
|
|
|
|
if v, ok := m["nbf"]; ok {
|
|
|
|
if notBefore, ok := v.(NumericDate); ok {
|
|
|
|
if e.Time.Add(leeway).Before(notBefore.Time()) {
|
|
|
|
return ErrNotValidYet
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := m["exp"]; ok {
|
|
|
|
if exp, ok := v.(int64); ok {
|
|
|
|
if e.Time.Add(-leeway).Before(time.Unix(exp, 0)) {
|
|
|
|
return ErrExpired
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := m["iat"]; ok {
|
|
|
|
if issuedAt, ok := v.(int64); ok {
|
|
|
|
if e.Time.Add(leeway).Before(time.Unix(issuedAt, 0)) {
|
|
|
|
return ErrIssuedInTheFuture
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2020-05-27 11:02:17 +02:00
|
|
|
// WriteToken is a helper which just generates(calls the `Token` method) and writes
|
|
|
|
// a new token to the client in plain text format.
|
|
|
|
//
|
|
|
|
// Use the `Token` method to get a new generated token raw string value.
|
|
|
|
func (j *JWT) WriteToken(ctx context.Context, claims interface{}) error {
|
|
|
|
token, err := j.Token(claims)
|
|
|
|
if err != nil {
|
|
|
|
ctx.StatusCode(500)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = ctx.WriteString(token)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2020-06-07 14:26:06 +02:00
|
|
|
// ErrMissing when token cannot be extracted from the request.
|
|
|
|
ErrMissing = errors.New("token is missing")
|
|
|
|
// ErrExpired indicates that token is used after expiry time indicated in exp claim.
|
|
|
|
ErrExpired = errors.New("token is expired (exp)")
|
|
|
|
// ErrNotValidYet indicates that token is used before time indicated in nbf claim.
|
|
|
|
ErrNotValidYet = errors.New("token not valid yet (nbf)")
|
|
|
|
// ErrIssuedInTheFuture indicates that the iat field is in the future.
|
|
|
|
ErrIssuedInTheFuture = errors.New("token issued in the future (iat)")
|
2020-05-27 11:02:17 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
claimsValidator interface {
|
|
|
|
ValidateWithLeeway(e jwt.Expected, leeway time.Duration) error
|
|
|
|
}
|
2020-06-07 14:26:06 +02:00
|
|
|
claimsAlternativeValidator interface { // to keep iris-contrib/jwt MapClaims compatible.
|
2020-05-27 11:02:17 +02:00
|
|
|
Validate() error
|
|
|
|
}
|
2020-06-07 14:26:06 +02:00
|
|
|
claimsContextValidator interface {
|
|
|
|
Validate(ctx context.Context) error
|
|
|
|
}
|
2020-05-27 11:02:17 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// IsValidated reports whether a token is already validated through
|
|
|
|
// `VerifyToken`. It returns true when the claims are compatible
|
|
|
|
// validators: a `Claims` value or a value that implements the `Validate() error` method.
|
|
|
|
func IsValidated(ctx context.Context) bool { // see the `ReadClaims`.
|
|
|
|
return ctx.Values().Get(needsValidationContextKey) == nil
|
|
|
|
}
|
|
|
|
|
2020-06-01 05:57:52 +02:00
|
|
|
func validateClaims(ctx context.Context, claims interface{}) (err error) {
|
2020-06-07 14:26:06 +02:00
|
|
|
switch c := claims.(type) {
|
2020-05-27 11:02:17 +02:00
|
|
|
case claimsValidator:
|
2020-06-07 14:26:06 +02:00
|
|
|
err = c.ValidateWithLeeway(jwt.Expected{Time: time.Now()}, 0)
|
2020-05-27 11:02:17 +02:00
|
|
|
case claimsAlternativeValidator:
|
2020-06-07 14:26:06 +02:00
|
|
|
err = c.Validate()
|
|
|
|
case claimsContextValidator:
|
|
|
|
err = c.Validate(ctx)
|
|
|
|
case *json.RawMessage:
|
|
|
|
// if the data type is raw message (json []byte)
|
|
|
|
// then it should contain exp (and iat and nbf) keys.
|
|
|
|
// Unmarshal raw message to validate against.
|
|
|
|
v := new(Claims)
|
|
|
|
err = json.Unmarshal(*c, v)
|
|
|
|
if err == nil {
|
|
|
|
return validateClaims(ctx, v)
|
|
|
|
}
|
2020-05-27 11:02:17 +02:00
|
|
|
default:
|
|
|
|
ctx.Values().Set(needsValidationContextKey, struct{}{})
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
2020-06-07 14:26:06 +02:00
|
|
|
switch err {
|
|
|
|
case jwt.ErrExpired:
|
|
|
|
return ErrExpired
|
|
|
|
case jwt.ErrNotValidYet:
|
|
|
|
return ErrNotValidYet
|
|
|
|
case jwt.ErrIssuedInTheFuture:
|
|
|
|
return ErrIssuedInTheFuture
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// VerifyToken verifies (and decrypts) the request token,
|
|
|
|
// it also validates and binds the parsed token's claims to the "claimsPtr" (destination).
|
|
|
|
// It does return a nil error on success.
|
|
|
|
func (j *JWT) VerifyToken(ctx context.Context, claimsPtr interface{}) error {
|
|
|
|
var token string
|
|
|
|
|
|
|
|
for _, extract := range j.Extractors {
|
|
|
|
if token = extract(ctx); token != "" {
|
|
|
|
break // ok we found it.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if token == "" {
|
2020-06-07 14:26:06 +02:00
|
|
|
return ErrMissing
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
parsedToken *jwt.JSONWebToken
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
if j.DecriptionKey != nil {
|
|
|
|
t, cerr := jwt.ParseSignedAndEncrypted(token)
|
|
|
|
if cerr != nil {
|
|
|
|
return cerr
|
|
|
|
}
|
|
|
|
|
|
|
|
parsedToken, err = t.Decrypt(j.DecriptionKey)
|
|
|
|
} else {
|
|
|
|
parsedToken, err = jwt.ParseSigned(token)
|
|
|
|
}
|
|
|
|
if err != nil {
|
2020-06-07 14:26:06 +02:00
|
|
|
return err
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if err = parsedToken.Claims(j.VerificationKey, claimsPtr); err != nil {
|
2020-06-07 14:26:06 +02:00
|
|
|
return err
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return validateClaims(ctx, claimsPtr)
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
// ClaimsContextKey is the context key which the jwt claims are stored from the `Verify` method.
|
|
|
|
ClaimsContextKey = "iris.jwt.claims"
|
|
|
|
needsValidationContextKey = "iris.jwt.claims.unvalidated"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Verify is a middleware. It verifies and optionally decrypts an incoming request token.
|
|
|
|
// It does write a 401 unauthorized status code if verification or decryption failed.
|
|
|
|
// It calls the `ctx.Next` on verified requests.
|
|
|
|
//
|
|
|
|
// See `VerifyToken` instead to verify, decrypt, validate and acquire the claims at once.
|
|
|
|
//
|
|
|
|
// A call of `ReadClaims` is required to validate and acquire the jwt claims
|
|
|
|
// on the next request.
|
|
|
|
func (j *JWT) Verify(ctx context.Context) {
|
|
|
|
var raw json.RawMessage
|
|
|
|
if err := j.VerifyToken(ctx, &raw); err != nil {
|
|
|
|
ctx.StopWithStatus(401)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Values().Set(ClaimsContextKey, raw)
|
|
|
|
ctx.Next()
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadClaims binds the "claimsPtr" (destination)
|
|
|
|
// to the verified (and decrypted) claims.
|
|
|
|
// The `Verify` method should be called first (registered as middleware).
|
|
|
|
func ReadClaims(ctx context.Context, claimsPtr interface{}) error {
|
|
|
|
v := ctx.Values().Get(ClaimsContextKey)
|
|
|
|
if v == nil {
|
2020-06-07 14:26:06 +02:00
|
|
|
return ErrMissing
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
raw, ok := v.(json.RawMessage)
|
|
|
|
if !ok {
|
2020-06-07 14:26:06 +02:00
|
|
|
return ErrMissing
|
2020-05-27 11:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
err := json.Unmarshal(raw, claimsPtr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !IsValidated(ctx) {
|
2020-06-07 14:26:06 +02:00
|
|
|
// If already validated on `Verify/VerifyToken`
|
|
|
|
// then no need to perform the check again.
|
2020-05-27 11:02:17 +02:00
|
|
|
ctx.Values().Remove(needsValidationContextKey)
|
|
|
|
return validateClaims(ctx, claimsPtr)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-06-01 05:57:52 +02:00
|
|
|
|
|
|
|
// Get returns and validates (if not already) the claims
|
|
|
|
// stored on request context's values storage.
|
|
|
|
//
|
|
|
|
// Should be used instead of the `ReadClaims` method when
|
|
|
|
// a custom verification middleware was registered (see the `Verify` method for an example).
|
|
|
|
//
|
|
|
|
// Usage:
|
|
|
|
// j := jwt.New(...)
|
|
|
|
// [...]
|
|
|
|
// app.Use(func(ctx iris.Context) {
|
|
|
|
// var claims CustomClaims_or_jwt.Claims
|
|
|
|
// if err := j.VerifyToken(ctx, &claims); err != nil {
|
|
|
|
// ctx.StopWithStatus(iris.StatusUnauthorized)
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// ctx.Values().Set(jwt.ClaimsContextKey, claims)
|
|
|
|
// ctx.Next()
|
|
|
|
// })
|
|
|
|
// [...]
|
|
|
|
// app.Post("/restricted", func(ctx iris.Context){
|
|
|
|
// v, err := jwt.Get(ctx)
|
|
|
|
// [handle error...]
|
|
|
|
// claims,ok := v.(CustomClaims_or_jwt.Claims)
|
|
|
|
// if !ok {
|
|
|
|
// [do you support more than one type of claims? Handle here]
|
|
|
|
// }
|
|
|
|
// [use claims...]
|
|
|
|
// })
|
|
|
|
func Get(ctx context.Context) (interface{}, error) {
|
|
|
|
claims := ctx.Values().Get(ClaimsContextKey)
|
|
|
|
if claims == nil {
|
2020-06-07 14:26:06 +02:00
|
|
|
return nil, ErrMissing
|
2020-06-01 05:57:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if !IsValidated(ctx) {
|
|
|
|
ctx.Values().Remove(needsValidationContextKey)
|
|
|
|
err := validateClaims(ctx, claims)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return claims, nil
|
|
|
|
}
|