Skip to content

Commit dcc6ce7

Browse files
author
khanh.nguyen
committed
request signature allows skipping url_hash claim validation
1 parent 54e71dd commit dcc6ce7

File tree

3 files changed

+43
-12
lines changed

3 files changed

+43
-12
lines changed

signature_jwt/claims.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Claims struct {
2323
receivedTime time.Time
2424
correctPayloadHash string
2525
correctURLHash string
26+
skipURLValidation bool
2627

2728
Issuer string `json:"iss"`
2829
NotBefore int64 `json:"nbf"`
@@ -53,7 +54,7 @@ func (c Claims) Valid() error {
5354
errs = append(errs, "claim jti is empty or missing")
5455
}
5556

56-
if c.correctURLHash != "" && c.correctURLHash != c.URLHash {
57+
if !c.skipURLValidation && c.correctURLHash != c.URLHash {
5758
errs = append(errs, "claim url_hash is invalid")
5859
}
5960

signature_jwt/signature.go

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Package signature_jwt implements signature verification for MessageBird webhooks.
33
44
To use define a new validator using your MessageBird Signing key. Can be
5-
retrieved through https://dashboard.messagebird.com/developers/settings.
5+
retrieved from https://dashboard.messagebird.com/developers/settings.
66
This is NOT your API key.
77
88
You can use the ValidateRequest method, just pass the request and base url as parameters:
@@ -18,6 +18,8 @@ Or use the handler as a middleware for your server:
1818
http.Handle("/path", validator.Validate(YourHandler, baseUrl))
1919
2020
It will reject the requests that contain invalid signatures.
21+
22+
For more information, see https://developers.messagebird.com/docs/verify-http-requests
2123
*/
2224
package signature_jwt
2325

@@ -53,26 +55,53 @@ var allowedMethods = []string{
5355
type Validator struct {
5456
parser jwt.Parser
5557
keyFn jwt.Keyfunc
58+
59+
skipURLValidation bool
60+
}
61+
62+
type ValidatorOption func(*Validator)
63+
64+
// SkipURLValidation instructs Validator to not validate url_hash claim.
65+
// It is recommended to not skip URL validation to ensure high security.
66+
// but the ability to skip URL validation is necessary in some cases, e.g.
67+
// your service is behind proxy or when you want to validate it yourself.
68+
// Note that if enabled, no query parameters should be trusted.
69+
func SkipURLValidation() ValidatorOption {
70+
return func(c *Validator) {
71+
c.skipURLValidation = true
72+
}
5673
}
5774

5875
// NewValidator returns a signature validator object.
59-
func NewValidator(signingKey string) *Validator {
60-
return &Validator{
76+
// Signing key can be retrieved from
77+
// https://dashboard.messagebird.com/developers/settings.
78+
// Note that this is NOT your API key.
79+
func NewValidator(signingKey string, opts ...ValidatorOption) *Validator {
80+
validator := &Validator{
6181
parser: jwt.Parser{
6282
ValidMethods: allowedMethods,
6383
},
6484
keyFn: func(*jwt.Token) (interface{}, error) { return []byte(signingKey), nil },
6585
}
86+
87+
for _, opt := range opts {
88+
opt(validator)
89+
}
90+
91+
return validator
6692
}
6793

68-
// ValidateSignature is a method that takes care of the signature validation of
69-
// incoming requests.
70-
func (v *Validator) ValidateSignature(signature, url string, payload []byte) (*jwt.Token, error) {
94+
// ValidateSignature returns the signature token claims when the signature
95+
// is validated successfully. Otherwise, an error is returned.
96+
// The provided url is the raw url including the protocol, hostname and
97+
// query string, e.g. https://example.com/?example=42.
98+
func (v *Validator) ValidateSignature(signature, url string, payload []byte) (jwt.Claims, error) {
7199
claims := Claims{
72-
receivedTime: TimeFunc(),
100+
receivedTime: TimeFunc(),
101+
skipURLValidation: v.skipURLValidation,
73102
}
74103

75-
if url != "" {
104+
if !v.skipURLValidation && url != "" {
76105
claims.correctURLHash = sha256Hash([]byte(url))
77106
}
78107
if payload != nil && len(payload) != 0 {
@@ -82,7 +111,7 @@ func (v *Validator) ValidateSignature(signature, url string, payload []byte) (*j
82111
if token, err := v.parser.ParseWithClaims(signature, &claims, v.keyFn); err != nil {
83112
return nil, fmt.Errorf("invalid jwt: %w", err)
84113
} else {
85-
return token, nil
114+
return token.Claims, nil
86115
}
87116
}
88117

@@ -95,7 +124,7 @@ func (v *Validator) ValidateRequest(r *http.Request, baseURL string) error {
95124
}
96125

97126
var fullURL string
98-
if baseURL != "" {
127+
if !v.skipURLValidation && baseURL != "" {
99128
base, err := url.Parse(baseURL)
100129
if err != nil {
101130
return fmt.Errorf("error parsing base url: %v", err)

signature_jwt/signature_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,10 @@ func TestValidSignature(t *testing.T) {
110110
}
111111

112112
v := NewValidator(tc.Secret)
113-
_, err := v.ValidateSignature(tc.Token, tc.Url, []byte(tc.Payload))
113+
claims, err := v.ValidateSignature(tc.Token, tc.Url, []byte(tc.Payload))
114114
if tc.Valid {
115115
assert.NoError(t, err)
116+
assert.NotEmpty(t, claims)
116117
return
117118
}
118119
assert.EqualError(t, err, tc.Reason)

0 commit comments

Comments
 (0)