forked from nytimes/gizmo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
verify.go
162 lines (133 loc) · 4.16 KB
/
verify.go
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
package auth // import "github.com/NYTimes/gizmo/auth"
import (
"context"
"encoding/base64"
"encoding/json"
"net/http"
"strings"
"time"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/pkg/errors"
"golang.org/x/oauth2/jws"
)
// Verifier is a generic tool for verifying JWT tokens.
type Verifier struct {
ks PublicKeySource
df ClaimsDecoderFunc
vf VerifyFunc
skewAllowance int64
}
// ErrBadCreds will always be wrapped when a user's
// credentials are unexpected. This is so that we can
// distinguish between a client error from a server error
var ErrBadCreds = errors.New("bad credentials")
var defaultSkewAllowance = time.Minute * 5
// ClaimSetter is an interface for all incoming claims to implement. This ensures the
// basic format used by the `jws` package.
type ClaimSetter interface {
BaseClaims() *jws.ClaimSet
}
// ClaimsDecoderFunc will expect to convert a JSON payload into the appropriate claims
// type.
type ClaimsDecoderFunc func(context.Context, []byte) (ClaimSetter, error)
// VerifyFunc will be called by the Verify if all other checks on the token pass.
// Developers should use this to encapsulate any business logic involved with token
// verification.
type VerifyFunc func(context.Context, interface{}) bool
// NewVerifier returns a genric Verifier that will use the given funcs and key source.
func NewVerifier(ks PublicKeySource, df ClaimsDecoderFunc, vf VerifyFunc) *Verifier {
return &Verifier{
ks: ks,
df: df,
vf: vf,
skewAllowance: int64(defaultSkewAllowance.Seconds()),
}
}
// VerifyInboundKitContext is meant to be used within a go-kit stack that has populated
// the context with common headers, specficially
// kit/transport/http.ContextKeyRequestAuthorization.
func (c Verifier) VerifyInboundKitContext(ctx context.Context) (bool, error) {
authHdr, ok := ctx.Value(httptransport.ContextKeyRequestAuthorization).(string)
if !ok {
return false, errors.New("auth header did not exist")
}
token, err := parseHeader(authHdr)
if err != nil {
return false, err
}
return c.Verify(ctx, token)
}
// VerifyRequest will pull the token from the "Authorization" header of the inbound
// request then decode and verify it.
func (c Verifier) VerifyRequest(r *http.Request) (bool, error) {
token, err := GetAuthorizationToken(r)
if err != nil {
return false, err
}
return c.Verify(r.Context(), token)
}
// Verify will accept an opaque JWT token, decode it and verify it.
func (c Verifier) Verify(ctx context.Context, token string) (bool, error) {
hdr, rawPayload, err := decodeToken(token)
if err != nil {
return false, errors.Wrap(ErrBadCreds, err.Error())
}
keys, err := c.ks.Get(ctx)
if err != nil {
return false, err
}
key, err := keys.GetKey(hdr.KeyID)
if err != nil {
return false, err
}
err = jws.Verify(token, key)
if err != nil {
return false, errors.Wrap(ErrBadCreds, err.Error())
}
// use claims decoder func
clmstr, err := c.df(ctx, rawPayload)
if err != nil {
return false, err
}
claims := clmstr.BaseClaims()
nowUnix := TimeNow().Unix()
if nowUnix < (claims.Iat - c.skewAllowance) {
return false, errors.New("invalid issue time")
}
if nowUnix > (claims.Exp + c.skewAllowance) {
return false, errors.Wrap(ErrBadCreds, "invalid expiration time")
}
return c.vf(ctx, clmstr), nil
}
func decodeToken(token string) (*jws.Header, []byte, error) {
s := strings.Split(token, ".")
if len(s) != 3 {
return nil, nil, errors.New("invalid token")
}
dh, err := base64.RawURLEncoding.DecodeString(s[0])
if err != nil {
return nil, nil, err
}
var h jws.Header
err = json.Unmarshal(dh, &h)
if err != nil {
return nil, nil, err
}
dcs, err := base64.RawURLEncoding.DecodeString(s[1])
if err != nil {
return nil, nil, err
}
return &h, dcs, nil
}
func parseHeader(hdr string) (string, error) {
auths := strings.Split(hdr, " ")
if len(auths) != 2 {
return "", errors.New("auth header invalid format")
}
return auths[1], nil
}
// GetAuthorizationToken will pull the Authorization header from the given request and
// attempt to retrieve the token within it.
func GetAuthorizationToken(r *http.Request) (string, error) {
return parseHeader(r.Header.Get("Authorization"))
}