/
token.go
170 lines (150 loc) · 4.89 KB
/
token.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
163
164
165
166
167
168
169
170
package auth
// auth module
//
// Copyright (c) 2024 - Valentin Kuznetsov <vkuznet AT gmail dot com>
//
// Useful materials:
// https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2
// https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-token-claims
// https://fusionauth.io/articles/tokens/jwt-components-explained
import (
"encoding/hex"
"errors"
"fmt"
"log"
"net/http"
"strings"
"time"
jwt "github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
)
// type Response struct {
// Status string `json:"status"`
// Uid int `json:"uid,omitempty"`
// Error string `json:"error,omitempty"`
// }
// CustomClaims defines application specific claims
type CustomClaims struct {
User string `json:"user"`
Scope string `json:"scope"`
Kind string `json:"kind"`
Roles []string `json:"roles"`
Application string `json:"application"`
}
// String provides string representations of Custom claims
func (c *CustomClaims) String() string {
var out []string
if c.User != "" {
out = append(out, fmt.Sprintf("User:%s", c.User))
}
if c.Scope != "" {
out = append(out, fmt.Sprintf("Scope:%s", c.Scope))
}
if c.Kind != "" {
out = append(out, fmt.Sprintf("Kind:%s", c.Kind))
}
if len(c.Roles) != 0 {
out = append(out, fmt.Sprintf("Roles:%sv", c.Roles))
}
if c.Application != "" {
out = append(out, fmt.Sprintf("Application:%s", c.Application))
}
return strings.Join(out, ", ")
}
// Claims defines our JWT claims
type Claims struct {
jwt.RegisteredClaims
CustomClaims CustomClaims `json:"custom_claims"`
}
// Token represents access token structure
type Token struct {
AccessToken string `json:"access_token"`
Expires int64 `json:"expires_in"`
Scope string `json:"scope"`
TokenType string `json:"token_type"`
}
// Validate performs token validation
func (t *Token) Validate(clientId string) error {
// validate our token
var jwtKey = []byte(clientId)
claims := &Claims{}
tkn, err := jwt.ParseWithClaims(t.AccessToken, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
if err == jwt.ErrSignatureInvalid {
return errors.New("invalid signature")
log.Fatal(err)
}
return err
}
if !tkn.Valid {
return errors.New("invalid token")
}
return nil
}
// TokenClaims returns token claims
func TokenClaims(accessToken, clientId string) (*Claims, error) {
var jwtKey = []byte(clientId)
claims := &Claims{}
tkn, err := jwt.ParseWithClaims(accessToken, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
if err == jwt.ErrSignatureInvalid {
return claims, err
// log.Println("ERROR", err)
}
}
if tkn == nil {
err := errors.New("invalid or empty token")
return claims, err
}
if !tkn.Valid {
err := errors.New("invalid token")
return claims, err
}
return claims, nil
}
// JWTAccessToken generates JWT access token with custom claims
// https://blog.canopas.com/jwt-in-golang-how-to-implement-token-based-authentication-298c89a26ffd
func JWTAccessToken(secretKey string, expiresAt int64, customClaims CustomClaims) (string, error) {
var sub, aud string
if uuid, err := uuid.NewRandom(); err == nil {
sub = hex.EncodeToString(uuid[:])
}
if uuid, err := uuid.NewRandom(); err == nil {
aud = hex.EncodeToString(uuid[:])
}
claims := Claims{
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "CHESS Authz server",
// the `sub` (Subject) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2
Subject: sub,
// the `aud` (Audience) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3
Audience: jwt.ClaimStrings{aud},
// the `exp` (Expiration Time) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiresAt) * time.Second)),
// the `nbf` (Not Before) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5
// NotBefore *NumericDate `json:"nbf,omitempty"`
// the `iat` (Issued At) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6
IssuedAt: jwt.NewNumericDate(time.Now()),
// the `jti` (JWT ID) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7
// ID string `json:"jti,omitempty"`
},
CustomClaims: customClaims,
}
// generate a string using claims and HS256 algorithm
tokenString := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
// sign the generated key using secretKey
// SignedString declared as interface{} but should accept []byte
// see https://github.com/dgrijalva/jwt-go/issues/65
token, err := tokenString.SignedString([]byte(secretKey))
return token, err
}
// Helper function to extract bearer token from http request
func BearerToken(r *http.Request) string {
token := strings.TrimPrefix(r.Header.Get("Authorization"), "bearer ")
token = strings.TrimPrefix(token, "Bearer ")
return token
}