Exemple #1
0
// New returns a middleware to be used with the JWTSecurity DSL definitions of goa.  It supports the
// scopes claim in the JWT and ensures goa-defined Security DSLs are properly validated.
//
// The steps taken by the middleware are:
//
//     1. Validate the "Bearer" token present in the "Authorization" header against the key(s)
//        given to New
//     2. If scopes are defined in the design for the action validate them against the "scopes" JWT
//        claim
//
// The `exp` (expiration) and `nbf` (not before) date checks are validated by the JWT library.
//
// validationKeys can be one of these:
//
//     * a single string
//     * a single []byte
//     * a list of string
//     * a list of []byte
//     * a single rsa.PublicKey
//     * a list of rsa.PublicKey
//
// The type of the keys determine the algorithms that will be used to do the check.  The goal of
// having lists of keys is to allow for key rotation, still check the previous keys until rotation
// has been completed.
//
// You can define an optional function to do additional validations on the token once the signature
// and the claims requirements are proven to be valid.  Example:
//
//    validationHandler, _ := goa.NewMiddleware(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
//        token := jwt.ContextJWT(ctx)
//        if val, ok := token.Claims["is_uncle"].(string); !ok || val != "ben" {
//            return jwt.ErrJWTError("you are not uncle ben's")
//        }
//    })
//
// Mount the middleware with the generated UseXX function where XX is the name of the scheme as
// defined in the design, e.g.:
//
//    app.UseJWT(jwt.New("secret", validationHandler, app.NewJWTSecurity()))
//
func New(validationKeys interface{}, validationFunc goa.Middleware, scheme *goa.JWTSecurity) goa.Middleware {
	var algo string
	var rsaKeys []*rsa.PublicKey
	var hmacKeys []string

	switch keys := validationKeys.(type) {
	case []*rsa.PublicKey:
		rsaKeys = keys
		algo = "RS"
	case *rsa.PublicKey:
		rsaKeys = []*rsa.PublicKey{keys}
		algo = "RS"
	case string:
		hmacKeys = []string{keys}
		algo = "HS"
	case []string:
		hmacKeys = keys
		algo = "HS"
	default:
		panic("invalid parameter to `jwt.New()`, only accepts *rsa.publicKey, []*rsa.PublicKey (for RSA-based algorithms) or a signing secret string (for HS algorithms)")
	}

	return func(nextHandler goa.Handler) goa.Handler {
		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
			// TODO: implement the QUERY string handler too
			if scheme.In != goa.LocHeader {
				return fmt.Errorf("whoops, security scheme with location (in) %q not supported", scheme.In)
			}
			val := req.Header.Get(scheme.Name)
			if val == "" {
				return ErrJWTError(fmt.Sprintf("missing header %q", scheme.Name))
			}

			if !strings.HasPrefix(strings.ToLower(val), "bearer ") {
				return ErrJWTError(fmt.Sprintf("invalid or malformed %q header, expected 'Authorization: Bearer JWT-token...'", val))
			}

			incomingToken := strings.Split(val, " ")[1]

			var token *jwt.Token
			var err error
			switch algo {
			case "RS":
				token, err = validateRSAKeys(rsaKeys, algo, incomingToken)
			case "HS":
				token, err = validateHMACKeys(hmacKeys, algo, incomingToken)
			default:
				panic("how did this happen ? unsupported algo in jwt middleware")
			}
			if err != nil {
				return ErrJWTError(fmt.Sprintf("JWT validation failed: %s", err))
			}

			scopesInClaim, scopesInClaimList, err := parseClaimScopes(token)
			if err != nil {
				goa.LogError(ctx, err.Error())
				return ErrJWTError(err)
			}

			requiredScopes := goa.ContextRequiredScopes(ctx)

			for _, scope := range requiredScopes {
				if !scopesInClaim[scope] {
					msg := "authorization failed: required 'scopes' not present in JWT claim"
					return ErrJWTError(msg, "required", requiredScopes, "scopes", scopesInClaimList)
				}
			}

			ctx = WithJWT(ctx, token)
			if validationFunc != nil {
				nextHandler = validationFunc(nextHandler)
			}
			return nextHandler(ctx, rw, req)
		}
	}
}
Exemple #2
0
// New returns a middleware to be used with the JWTSecurity DSL definitions of goa.  It supports the
// scopes claim in the JWT and ensures goa-defined Security DSLs are properly validated.
//
// The steps taken by the middleware are:
//
//     1. Validate the "Bearer" token present in the "Authorization" header against the key(s)
//        given to New
//     2. If scopes are defined in the design for the action validate them against the "scopes" JWT
//        claim
//
// The `exp` (expiration) and `nbf` (not before) date checks are validated by the JWT library.
//
// validationKeys can be one of these:
//
//     * a single []byte
//     * a single string
//     * a slice of []byte
//     * a slice of string
//     * a single *rsa.PublicKey
//     * a slice of *rsa.PublicKey
//
// Keys of type string or []byte are interepreted according to the signing method defined in the JWT
// token (HMAC, RSA, etc.).
//
// You can define an optional function to do additional validations on the token once the signature
// and the claims requirements are proven to be valid.  Example:
//
//    validationHandler, _ := goa.NewMiddleware(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
//        token := jwt.ContextJWT(ctx)
//        if val, ok := token.Claims["is_uncle"].(string); !ok || val != "ben" {
//            return jwt.ErrJWTError("you are not uncle ben's")
//        }
//    })
//
// Mount the middleware with the generated UseXX function where XX is the name of the scheme as
// defined in the design, e.g.:
//
//    jwtResolver, _ := jwt.NewSimpleResolver("secret")
//    app.UseJWT(jwt.New(jwtResolver, validationHandler, app.NewJWTSecurity()))
//
func New(resolver KeyResolver, validationFunc goa.Middleware, scheme *goa.JWTSecurity) goa.Middleware {
	return func(nextHandler goa.Handler) goa.Handler {
		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
			// TODO: implement the QUERY string handler too
			if scheme.In != goa.LocHeader {
				return fmt.Errorf("whoops, security scheme with location (in) %q not supported", scheme.In)
			}
			val := req.Header.Get(scheme.Name)
			if val == "" {
				return ErrJWTError(fmt.Sprintf("missing header %q", scheme.Name))
			}

			if !strings.HasPrefix(strings.ToLower(val), "bearer ") {
				return ErrJWTError(fmt.Sprintf("invalid or malformed %q header, expected 'Authorization: Bearer JWT-token...'", val))
			}

			incomingToken := strings.Split(val, " ")[1]

			var (
				rsaKeys  []*rsa.PublicKey
				hmacKeys [][]byte
				keys     = resolver.SelectKeys(req)
			)
			{
				for _, key := range keys {
					switch k := key.(type) {
					case *rsa.PublicKey:
						rsaKeys = append(rsaKeys, k)
					case []byte:
						hmacKeys = append(hmacKeys, k)
					case string:
						hmacKeys = append(hmacKeys, []byte(k))
					}
				}
			}
			var (
				token     *jwt.Token
				err       error
				validated = false
			)

			if len(rsaKeys) > 0 {
				token, err = validateRSAKeys(rsaKeys, "RS", incomingToken)
				if err == nil {
					validated = true
				}
			}

			if !validated && len(hmacKeys) > 0 {
				token, err = validateHMACKeys(hmacKeys, "HS", incomingToken)
				if err == nil {
					validated = true
				}
			}

			if !validated {
				return ErrJWTError("JWT validation failed")
			}

			scopesInClaim, scopesInClaimList, err := parseClaimScopes(token)
			if err != nil {
				goa.LogError(ctx, err.Error())
				return ErrJWTError(err)
			}

			requiredScopes := goa.ContextRequiredScopes(ctx)

			for _, scope := range requiredScopes {
				if !scopesInClaim[scope] {
					msg := "authorization failed: required 'scopes' not present in JWT claim"
					return ErrJWTError(msg, "required", requiredScopes, "scopes", scopesInClaimList)
				}
			}

			ctx = WithJWT(ctx, token)
			if validationFunc != nil {
				nextHandler = validationFunc(nextHandler)
			}
			return nextHandler(ctx, rw, req)
		}
	}
}