Repository: go-playground/validator
Pull request #783 (2021-06-09 07:38:15)

Add support for jwt validation
## Fixes Or Enhances
Adds support for validating JSON Web Token (JWT) strings

**Make sure that you've checked the boxes below before you submit PR:**
- [x] Tests exist or have been written that cover this particular change.

@go-playground/validator-maintainers

coveralls: 
[![Coverage Status](https://coveralls.io/builds/41634401/badge)](https://coveralls.io/builds/41634401)

Coverage increased (+0.006%) to 75.384% when pulling **066af9d5fcc497c190ab2db3ba6bb13a80ef42bc on steveorourke:master** into **65bb1236771df9bc1630c78a43b0bfea10fe7122 on go-playground:master**.


Patch:
diff --git a/README.md b/README.md
index 7dcc5af..cdbb805 100644
--- a/README.md
+++ b/README.md
@@ -159,6 +159,7 @@ Baked-in Validations
 | isbn10 | International Standard Book Number 10 |
 | isbn13 | International Standard Book Number 13 |
 | json | JSON |
+| jwt | JSON Web Token (JWT) |
 | latitude | Latitude |
 | longitude | Longitude |
 | rgb | RGB String |
diff --git a/baked_in.go b/baked_in.go
index 224a1c2..b22c01a 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -181,6 +181,7 @@ var (
 		"url_encoded":                   isURLEncoded,
 		"dir":                           isDir,
 		"json":                          isJSON,
+		"jwt":                           isJWT,
 		"hostname_port":                 isHostnamePort,
 		"lowercase":                     isLowercase,
 		"uppercase":                     isUppercase,
@@ -2235,6 +2236,11 @@ func isJSON(fl FieldLevel) bool {
 	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
 }
 
+// isJWT is the validation function for validating if the current field's value is a valid JWT string.
+func isJWT(fl FieldLevel) bool {
+	return jWTRegex.MatchString(fl.Field().String())
+}
+
 // isHostnamePort validates a <dns>:<port> combination for fields typically used for socket address.
 func isHostnamePort(fl FieldLevel) bool {
 	val := fl.Field().String()
diff --git a/doc.go b/doc.go
index eafad0d..cf1376b 100644
--- a/doc.go
+++ b/doc.go
@@ -811,6 +811,12 @@ This validates that a string value is valid JSON
 
 	Usage: json
 
+JWT String
+
+This validates that a string value is a valid JWT
+
+	Usage: jwt
+
 File path
 
 This validates that a string value contains a valid file path and that
diff --git a/regexes.go b/regexes.go
index ddcf785..df00c4e 100644
--- a/regexes.go
+++ b/regexes.go
@@ -48,6 +48,7 @@ const (
 	uRLEncodedRegexString            = `^(?:[^%]|%[0-9A-Fa-f]{2})*$`
 	hTMLEncodedRegexString           = `&#[x]?([0-9a-fA-F]{2})|(&gt)|(&lt)|(&quot)|(&amp)+[;]?`
 	hTMLRegexString                  = `<[/]?([a-zA-Z]+).*?>`
+	jWTRegexString                   = "^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$"
 	splitParamsRegexString           = `'[^']*'|\S+`
 	bicRegexString                   = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$`
 )
@@ -98,6 +99,7 @@ var (
 	uRLEncodedRegex            = regexp.MustCompile(uRLEncodedRegexString)
 	hTMLEncodedRegex           = regexp.MustCompile(hTMLEncodedRegexString)
 	hTMLRegex                  = regexp.MustCompile(hTMLRegexString)
+	jWTRegex                   = regexp.MustCompile(jWTRegexString)
 	splitParamsRegex           = regexp.MustCompile(splitParamsRegexString)
 	bicRegex                   = regexp.MustCompile(bicRegexString)
 )
diff --git a/translations/en/en.go b/translations/en/en.go
index 3bbca51..b95f7dd 100644
--- a/translations/en/en.go
+++ b/translations/en/en.go
@@ -1284,6 +1284,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
 			translation: "{0} must be a valid json string",
 			override:    false,
 		},
+		{
+			tag:         "jwt",
+			translation: "{0} must be a valid jwt string",
+			override:    false,
+		},
 		{
 			tag:         "lowercase",
 			translation: "{0} must be a lowercase string",


Test patch:
diff --git a/translations/en/en_test.go b/translations/en/en_test.go
index fd8df77..0f20768 100644
--- a/translations/en/en_test.go
+++ b/translations/en/en_test.go
@@ -141,6 +141,7 @@ func TestTranslations(t *testing.T) {
 		UniqueArray       [3]string         `validate:"unique"`
 		UniqueMap         map[string]string `validate:"unique"`
 		JSONString        string            `validate:"json"`
+		JWTString         string            `validate:"jwt"`
 		LowercaseString   string            `validate:"lowercase"`
 		UppercaseString   string            `validate:"uppercase"`
 		Datetime          string            `validate:"datetime=2006-01-02"`
@@ -646,6 +647,10 @@ func TestTranslations(t *testing.T) {
 			ns:       "Test.JSONString",
 			expected: "JSONString must be a valid json string",
 		},
+		{
+			ns:       "Test.JWTString",
+			expected: "JWTString must be a valid jwt string",
+		},
 		{
 			ns:       "Test.LowercaseString",
 			expected: "LowercaseString must be a lowercase string",
diff --git a/validator_test.go b/validator_test.go
index 3f0ea8d..8e9dc03 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -10787,6 +10787,42 @@ func TestJSONValidation(t *testing.T) {
 	}, "Bad field type int")
 }
 
+func TestJWTValidation(t *testing.T) {
+	tests := []struct {
+		param    string
+		expected bool
+	}{
+		{"eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiZ29waGVyIn0.O_bROM_szPq9qBql-XDHMranHwP48ODdoLICWzqBr_U", true},
+		{"acb123-_.def456-_.ghi789-_", true},
+		{"eyJhbGciOiJOT05FIn0.e30.", true},
+		{"eyJhbGciOiJOT05FIn0.e30.\n", false},
+		{"\x00.\x00.\x00", false},
+		{"", false},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+
+		errs := validate.Var(test.param, "jwt")
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d jwt failed Error: %s", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d jwt failed Error: %s", i, errs)
+			} else {
+				val := getError(errs, "", "")
+				if val.Tag() != "jwt" {
+					t.Fatalf("Index: %d jwt failed Error: %s", i, errs)
+				}
+			}
+		}
+	}
+}
+
 func Test_hostnameport_validator(t *testing.T) {
 	type Host struct {
 		Addr string `validate:"hostname_port"`