From 781c8bd3b82c3cb8f726c1fde6962597594e9560 Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Wed, 10 Aug 2022 15:15:38 +0200 Subject: [PATCH 1/2] Implement a BearerExtractor This is a rather common extractor; it extracts the JWT from the HTTP Authorization header, expecting it to include the "Bearer " prefix. This patterns is rather common and this snippet is repeated in enough applications that it's probably best to just include it upstream and allow reusing it. --- request/extractor.go | 14 ++++++++++++++ request/extractor_test.go | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/request/extractor.go b/request/extractor.go index 1dbc59a6..6183080e 100644 --- a/request/extractor.go +++ b/request/extractor.go @@ -3,6 +3,7 @@ package request import ( "errors" "net/http" + "strings" ) // Errors @@ -79,3 +80,16 @@ func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) { return "", err } } + +// BearerExtractor extracts a token from the Authorization header. +// The header is expected to match the format "Bearer XX", where "XX" is the +// JWT token. +type BearerExtractor struct{} + +func (e BearerExtractor) ExtractToken(req *http.Request) (string, error) { + tokenHeader := req.Header.Get("Authorization") + if tokenHeader == "" || !strings.HasPrefix(tokenHeader, "Bearer ") { + return "", ErrNoTokenInRequest + } + return strings.TrimPrefix(tokenHeader, "Bearer "), nil +} diff --git a/request/extractor_test.go b/request/extractor_test.go index e3bbb0a3..b856eebe 100644 --- a/request/extractor_test.go +++ b/request/extractor_test.go @@ -89,3 +89,18 @@ func makeExampleRequest(method, path string, headers map[string]string, urlArgs } return r } + +func TestBearerExtractor(t *testing.T) { + request := makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearer 123"}, nil) + token, err := BearerExtractor{}.ExtractToken(request) + if err != nil || token != "123" { + t.Errorf("ExtractToken did not return token, returned: %v, %v", token, err) + } + + request = makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearo 123"}, nil) + + token, err = BearerExtractor{}.ExtractToken(request) + if err == nil || token != "" { + t.Errorf("ExtractToken did not return error, returned: %v, %v", token, err) + } +} From d634c29caf0e6a6dc37b4b0d69d835bef413ec97 Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Mon, 15 Aug 2022 13:32:22 +0200 Subject: [PATCH 2/2] Ignore case-sensitivity for "Bearer" --- request/extractor.go | 6 ++++-- request/extractor_test.go | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/request/extractor.go b/request/extractor.go index 6183080e..57de8b77 100644 --- a/request/extractor.go +++ b/request/extractor.go @@ -88,8 +88,10 @@ type BearerExtractor struct{} func (e BearerExtractor) ExtractToken(req *http.Request) (string, error) { tokenHeader := req.Header.Get("Authorization") - if tokenHeader == "" || !strings.HasPrefix(tokenHeader, "Bearer ") { + // The usual convention is for "Bearer" to be title-cased. However, there's no + // strict rule around this, and it's best to follow the robustness principle here. + if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), "bearer ") { return "", ErrNoTokenInRequest } - return strings.TrimPrefix(tokenHeader, "Bearer "), nil + return tokenHeader[7:], nil } diff --git a/request/extractor_test.go b/request/extractor_test.go index b856eebe..5be2b5f3 100644 --- a/request/extractor_test.go +++ b/request/extractor_test.go @@ -91,16 +91,21 @@ func makeExampleRequest(method, path string, headers map[string]string, urlArgs } func TestBearerExtractor(t *testing.T) { - request := makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearer 123"}, nil) + request := makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearer ToKen"}, nil) token, err := BearerExtractor{}.ExtractToken(request) - if err != nil || token != "123" { + if err != nil || token != "ToKen" { t.Errorf("ExtractToken did not return token, returned: %v, %v", token, err) } - request = makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearo 123"}, nil) - + request = makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearo ToKen"}, nil) token, err = BearerExtractor{}.ExtractToken(request) if err == nil || token != "" { t.Errorf("ExtractToken did not return error, returned: %v, %v", token, err) } + + request = makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "BeArEr HeLO"}, nil) + token, err = BearerExtractor{}.ExtractToken(request) + if err != nil || token != "HeLO" { + t.Errorf("ExtractToken did not return token, returned: %v, %v", token, err) + } }