Repository: labstack/echo (go)

Issue:
Add form field support for JWT middleware
### Issue Description
I've added a new TokenLookup value `form:<name>` to use an HTML form field as a source of JWT tokens. It's trivial and I can open a pull request but I feel like posting an issue first is a good idea.

### Checklist

- [x] Dependencies installed
- [x] No typos
- [x] Searched existing issues and docs

### Rationale
I found it a bit hard to create printed forms securely in my web apps. They're opened in new tabs and contain simple HTML. I need to pass some parameters to them (according to the user's input) as well as the authentication information. Since I use `Authorization: Bearer` header for the latter in the webapp itself I need another way to pass the token without using JS (and it's not possible to open a new tab with a custom HTTP header). So it has to be either a GET or POST parameter.

I really don't want to pass a token as a query parameter because it can easily leak this way. I also don't want to use cookies because it opens a CSRF attack possibility. I ended up with a hidden form input that contains the token (in the JS webapp) that opens a new tab with POST method and passes other user-defined values as query parameters so they're visible and easy to reproduce. But Echo can't extract the token from a form field so I added that and it works just fine.

I also wrote tests (expanded the existing test actually) for this new functionality. Tell me if it's worth adding and I'll post the patch.

Pull request description:
Support form fields in jwt middleware
Support passing JWT tokens in form fields
Closes #1697

Patch:
diff --git a/middleware/jwt.go b/middleware/jwt.go
index bab00c9..da00ea5 100644
--- a/middleware/jwt.go
+++ b/middleware/jwt.go
@@ -57,6 +57,7 @@ type (
 		// - "query:<name>"
 		// - "param:<name>"
 		// - "cookie:<name>"
+		// - "form:<name>"
 		TokenLookup string
 
 		// AuthScheme to be used in the Authorization header.
@@ -167,6 +168,8 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
 		extractor = jwtFromParam(parts[1])
 	case "cookie":
 		extractor = jwtFromCookie(parts[1])
+	case "form":
+		extractor = jwtFromForm(parts[1])
 	}
 
 	return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -266,3 +269,14 @@ func jwtFromCookie(name string) jwtExtractor {
 		return cookie.Value, nil
 	}
 }
+
+// jwtFromForm returns a `jwtExtractor` that extracts token from the form field.
+func jwtFromForm(name string) jwtExtractor {
+	return func(c echo.Context) (string, error) {
+		field := c.FormValue(name)
+		if field == "" {
+			return "", ErrJWTMissing
+		}
+		return field, nil
+	}
+}


Test patch:
diff --git a/middleware/jwt_test.go b/middleware/jwt_test.go
index ce44f9c..205721a 100644
--- a/middleware/jwt_test.go
+++ b/middleware/jwt_test.go
@@ -3,6 +3,8 @@ package middleware
 import (
 	"net/http"
 	"net/http/httptest"
+	"net/url"
+	"strings"
 	"testing"
 
 	"github.com/dgrijalva/jwt-go"
@@ -75,6 +77,7 @@ func TestJWT(t *testing.T) {
 		reqURL     string // "/" if empty
 		hdrAuth    string
 		hdrCookie  string // test.Request doesn't provide SetCookie(); use name=val
+		formValues map[string]string
 		info       string
 	}{
 		{
@@ -192,12 +195,48 @@ func TestJWT(t *testing.T) {
 			expErrCode: http.StatusBadRequest,
 			info:       "Empty cookie",
 		},
+		{
+			config: JWTConfig{
+				SigningKey:  validKey,
+				TokenLookup: "form:jwt",
+			},
+			formValues: map[string]string{"jwt": token},
+			info:       "Valid form method",
+		},
+		{
+			config: JWTConfig{
+				SigningKey:  validKey,
+				TokenLookup: "form:jwt",
+			},
+			expErrCode: http.StatusUnauthorized,
+			formValues: map[string]string{"jwt": "invalid"},
+			info:       "Invalid token with form method",
+		},
+		{
+			config: JWTConfig{
+				SigningKey:  validKey,
+				TokenLookup: "form:jwt",
+			},
+			expErrCode: http.StatusBadRequest,
+			info:       "Empty form field",
+		},
 	} {
 		if tc.reqURL == "" {
 			tc.reqURL = "/"
 		}
 
-		req := httptest.NewRequest(http.MethodGet, tc.reqURL, nil)
+		var req *http.Request
+		if len(tc.formValues) > 0 {
+			form := url.Values{}
+			for k, v := range tc.formValues {
+				form.Set(k, v)
+			}
+			req = httptest.NewRequest(http.MethodPost, tc.reqURL, strings.NewReader(form.Encode()))
+			req.Header.Set(echo.HeaderContentType, "application/x-www-form-urlencoded")
+			req.ParseForm()
+		} else {
+			req = httptest.NewRequest(http.MethodGet, tc.reqURL, nil)
+		}
 		res := httptest.NewRecorder()
 		req.Header.Set(echo.HeaderAuthorization, tc.hdrAuth)
 		req.Header.Set(echo.HeaderCookie, tc.hdrCookie)