1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
|
package jwt
import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/lestrrat-go/jwx/v2/internal/pool"
)
// ParseCookie parses a JWT stored in a http.Cookie with the given name.
// If the specified cookie is not found, http.ErrNoCookie is returned.
func ParseCookie(req *http.Request, name string, options ...ParseOption) (Token, error) {
var dst **http.Cookie
//nolint:forcetypeassert
for _, option := range options {
switch option.Ident() {
case identCookie{}:
dst = option.Value().(**http.Cookie)
}
}
cookie, err := req.Cookie(name)
if err != nil {
return nil, err
}
tok, err := ParseString(cookie.Value, options...)
if err != nil {
return nil, fmt.Errorf(`failed to parse token stored in cookie: %w`, err)
}
if dst != nil {
*dst = cookie
}
return tok, nil
}
// ParseHeader parses a JWT stored in a http.Header.
//
// For the header "Authorization", it will strip the prefix "Bearer " and will
// treat the remaining value as a JWT.
func ParseHeader(hdr http.Header, name string, options ...ParseOption) (Token, error) {
key := http.CanonicalHeaderKey(name)
v := strings.TrimSpace(hdr.Get(key))
if v == "" {
return nil, fmt.Errorf(`empty header (%s)`, key)
}
if key == "Authorization" {
// Authorization header is an exception. We strip the "Bearer " from
// the prefix
v = strings.TrimSpace(strings.TrimPrefix(v, "Bearer"))
}
return ParseString(v, options...)
}
// ParseForm parses a JWT stored in a url.Value.
func ParseForm(values url.Values, name string, options ...ParseOption) (Token, error) {
v := strings.TrimSpace(values.Get(name))
if v == "" {
return nil, fmt.Errorf(`empty value (%s)`, name)
}
return ParseString(v, options...)
}
// ParseRequest searches a http.Request object for a JWT token.
//
// Specifying WithHeaderKey() will tell it to search under a specific
// header key. Specifying WithFormKey() will tell it to search under
// a specific form field.
//
// If none of jwt.WithHeaderKey()/jwt.WithCookieKey()/jwt.WithFormKey() is
// used, "Authorization" header will be searched. If any of these options
// are specified, you must explicitly re-enable searching for "Authorization" header
// if you also want to search for it.
//
// # searches for "Authorization"
// jwt.ParseRequest(req)
//
// # searches for "x-my-token" ONLY.
// jwt.ParseRequest(req, jwt.WithHeaderKey("x-my-token"))
//
// # searches for "Authorization" AND "x-my-token"
// jwt.ParseRequest(req, jwt.WithHeaderKey("Authorization"), jwt.WithHeaderKey("x-my-token"))
//
// Cookies are searched using (http.Request).Cookie(). If you have multiple
// cookies with the same name, and you want to search for a specific one that
// (http.Request).Cookie() would not return, you will need to implement your
// own logic to extract the cookie and use jwt.ParseString().
func ParseRequest(req *http.Request, options ...ParseOption) (Token, error) {
var hdrkeys []string
var formkeys []string
var cookiekeys []string
var parseOptions []ParseOption
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identHeaderKey{}:
hdrkeys = append(hdrkeys, option.Value().(string))
case identFormKey{}:
formkeys = append(formkeys, option.Value().(string))
case identCookieKey{}:
cookiekeys = append(cookiekeys, option.Value().(string))
default:
parseOptions = append(parseOptions, option)
}
}
if len(hdrkeys) == 0 && len(formkeys) == 0 && len(cookiekeys) == 0 {
hdrkeys = append(hdrkeys, "Authorization")
}
mhdrs := pool.GetKeyToErrorMap()
defer pool.ReleaseKeyToErrorMap(mhdrs)
mfrms := pool.GetKeyToErrorMap()
defer pool.ReleaseKeyToErrorMap(mfrms)
mcookies := pool.GetKeyToErrorMap()
defer pool.ReleaseKeyToErrorMap(mcookies)
for _, hdrkey := range hdrkeys {
// Check presence via a direct map lookup
if _, ok := req.Header[http.CanonicalHeaderKey(hdrkey)]; !ok {
// if non-existent, not error
continue
}
tok, err := ParseHeader(req.Header, hdrkey, parseOptions...)
if err != nil {
mhdrs[hdrkey] = err
continue
}
return tok, nil
}
for _, name := range cookiekeys {
tok, err := ParseCookie(req, name, parseOptions...)
if err != nil {
if err == http.ErrNoCookie {
// not fatal
mcookies[name] = err
}
continue
}
return tok, nil
}
if cl := req.ContentLength; cl > 0 {
if err := req.ParseForm(); err != nil {
return nil, fmt.Errorf(`failed to parse form: %w`, err)
}
}
for _, formkey := range formkeys {
// Check presence via a direct map lookup
if _, ok := req.Form[formkey]; !ok {
// if non-existent, not error
continue
}
tok, err := ParseForm(req.Form, formkey, parseOptions...)
if err != nil {
mfrms[formkey] = err
continue
}
return tok, nil
}
// Everything below is a prelude to error reporting.
var triedHdrs strings.Builder
for i, hdrkey := range hdrkeys {
if i > 0 {
triedHdrs.WriteString(", ")
}
triedHdrs.WriteString(strconv.Quote(hdrkey))
}
var triedForms strings.Builder
for i, formkey := range formkeys {
if i > 0 {
triedForms.WriteString(", ")
}
triedForms.WriteString(strconv.Quote(formkey))
}
var triedCookies strings.Builder
for i, cookiekey := range cookiekeys {
if i > 0 {
triedCookies.WriteString(", ")
}
triedCookies.WriteString(strconv.Quote(cookiekey))
}
var b strings.Builder
b.WriteString(`failed to find a valid token in any location of the request (tried: `)
olen := b.Len()
if triedHdrs.Len() > 0 {
b.WriteString(`header keys: [`)
b.WriteString(triedHdrs.String())
b.WriteByte(']')
}
if triedForms.Len() > 0 {
if b.Len() > olen {
b.WriteString(", ")
}
b.WriteString("form keys: [")
b.WriteString(triedForms.String())
b.WriteByte(']')
}
if triedCookies.Len() > 0 {
if b.Len() > olen {
b.WriteString(", ")
}
b.WriteString("cookie keys: [")
b.WriteString(triedCookies.String())
b.WriteByte(']')
}
b.WriteByte(')')
lmhdrs := len(mhdrs)
lmfrms := len(mfrms)
lmcookies := len(mcookies)
var errors []interface{}
if lmhdrs > 0 || lmfrms > 0 || lmcookies > 0 {
b.WriteString(". Additionally, errors were encountered during attempts to parse")
if lmhdrs > 0 {
b.WriteString(" headers: (")
count := 0
for hdrkey, err := range mhdrs {
if count > 0 {
b.WriteString(", ")
}
b.WriteString("[header key: ")
b.WriteString(strconv.Quote(hdrkey))
b.WriteString(", error: %w]")
errors = append(errors, err)
count++
}
b.WriteString(")")
}
if lmcookies > 0 {
count := 0
b.WriteString(" cookies: (")
for cookiekey, err := range mcookies {
if count > 0 {
b.WriteString(", ")
}
b.WriteString("[cookie key: ")
b.WriteString(strconv.Quote(cookiekey))
b.WriteString(", error: %w]")
errors = append(errors, err)
count++
}
}
if lmfrms > 0 {
count := 0
b.WriteString(" forms: (")
for formkey, err := range mfrms {
if count > 0 {
b.WriteString(", ")
}
b.WriteString("[form key: ")
b.WriteString(strconv.Quote(formkey))
b.WriteString(", error: %w]")
errors = append(errors, err)
count++
}
}
}
return nil, fmt.Errorf(b.String(), errors...)
}
|