Skip to content

Commit 53fd20c

Browse files
committed
support token_user_id_claim option
1 parent 7fd9b53 commit 53fd20c

File tree

5 files changed

+124
-55
lines changed

5 files changed

+124
-55
lines changed

go.sum

-8
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
8282
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
8383
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
8484
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
85-
github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s=
86-
github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
8785
github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
8886
github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
8987
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
@@ -228,8 +226,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
228226
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
229227
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
230228
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
231-
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
232-
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
233229
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
234230
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
235231
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
@@ -262,8 +258,6 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
262258
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
263259
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
264260
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
265-
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
266-
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
267261
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
268262
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
269263
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -287,8 +281,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:
287281
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
288282
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
289283
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
290-
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
291-
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
292284
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
293285
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
294286
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=

internal/cli/token.go

+20-12
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,15 @@ func GenerateToken(config jwtverify.VerifierConfig, user string, ttlSeconds int6
2121
return "", fmt.Errorf("error creating HMAC signer: %w", err)
2222
}
2323
builder := jwt.NewBuilder(signer)
24-
claims := jwt.RegisteredClaims{
25-
Subject: user,
26-
IssuedAt: jwt.NewNumericDate(time.Now()),
24+
claims := jwtverify.ConnectTokenClaims{
25+
RegisteredClaims: jwt.RegisteredClaims{
26+
IssuedAt: jwt.NewNumericDate(time.Now()),
27+
},
28+
}
29+
if config.UserIDClaim != "" {
30+
claims.UserID = user
31+
} else {
32+
claims.Subject = user
2733
}
2834
if ttlSeconds > 0 {
2935
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Duration(ttlSeconds) * time.Second))
@@ -45,19 +51,21 @@ func GenerateSubToken(config jwtverify.VerifierConfig, user string, channel stri
4551
return "", fmt.Errorf("error creating HMAC signer: %w", err)
4652
}
4753
builder := jwt.NewBuilder(signer)
48-
claims := jwt.RegisteredClaims{
49-
Subject: user,
50-
IssuedAt: jwt.NewNumericDate(time.Now()),
54+
claims := jwtverify.SubscribeTokenClaims{
55+
RegisteredClaims: jwt.RegisteredClaims{
56+
IssuedAt: jwt.NewNumericDate(time.Now()),
57+
},
58+
Channel: channel,
59+
}
60+
if config.UserIDClaim != "" {
61+
claims.UserID = user
62+
} else {
63+
claims.Subject = user
5164
}
5265
if ttlSeconds > 0 {
5366
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Duration(ttlSeconds) * time.Second))
5467
}
55-
token, err := builder.Build(
56-
jwtverify.SubscribeTokenClaims{
57-
RegisteredClaims: claims,
58-
Channel: channel,
59-
},
60-
)
68+
token, err := builder.Build(claims)
6169
if err != nil {
6270
return "", err
6371
}

internal/jwtverify/token_verifier_jwt.go

+24-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"github.com/rs/zerolog/log"
2424
)
2525

26+
const alternativeUserIDClaim = "user_id"
27+
2628
type VerifierConfig struct {
2729
// HMACSecretKey is a secret key used to validate connection and subscription
2830
// tokens generated using HMAC. Zero value means that HMAC tokens won't be allowed.
@@ -56,6 +58,11 @@ type VerifierConfig struct {
5658
// IssuerRegex allows setting Issuer in form of Go language regex pattern. Regex groups
5759
// may be then used in constructing JWKSPublicEndpoint.
5860
IssuerRegex string
61+
62+
// UserIDClaim allows overriding default claim used to extract user ID from token. At this
63+
// moment only "user_id" alternative claim is supported due to how tokens are parsed.
64+
// By default, Centrifugo uses "sub".
65+
UserIDClaim string
5966
}
6067

6168
func (c VerifierConfig) Validate() error {
@@ -94,6 +101,7 @@ func NewTokenVerifierJWT(config VerifierConfig, ruleContainer *rule.Container) (
94101
issuerRe: issuerRe,
95102
audience: config.Audience,
96103
audienceRe: audienceRe,
104+
userIDClaim: config.UserIDClaim,
97105
}
98106

99107
algorithms, err := newAlgorithms(config.HMACSecretKey, config.RSAPublicKey, config.ECDSAPublicKey)
@@ -122,6 +130,7 @@ type VerifierJWT struct {
122130
audienceRe *regexp.Regexp
123131
issuer string
124132
issuerRe *regexp.Regexp
133+
userIDClaim string
125134
}
126135

127136
var (
@@ -180,6 +189,8 @@ type ConnectTokenClaims struct {
180189
Channels []string `json:"channels,omitempty"`
181190
Subs map[string]SubscribeOptions `json:"subs,omitempty"`
182191
Meta json.RawMessage `json:"meta,omitempty"`
192+
// UserID is only used instead of jwt.RegisteredClaims.Subject when explicitly configured.
193+
UserID string `json:"user_id,omitempty"`
183194
// Channel must never be set in connection tokens. We check this on verifying.
184195
Channel string `json:"channel,omitempty"`
185196
jwt.RegisteredClaims
@@ -191,6 +202,8 @@ type SubscribeTokenClaims struct {
191202
Channel string `json:"channel,omitempty"`
192203
Client string `json:"client,omitempty"`
193204
ExpireAt *int64 `json:"expire_at,omitempty"`
205+
// UserID is only used instead of jwt.RegisteredClaims.Subject when explicitly configured.
206+
UserID string `json:"user_id,omitempty"`
194207
}
195208

196209
type jwksManager struct{ *jwks.Manager }
@@ -579,13 +592,16 @@ func (verifier *VerifierJWT) VerifyConnectToken(t string, skipVerify bool) (Conn
579592
}
580593

581594
ct := ConnectToken{
582-
UserID: claims.RegisteredClaims.Subject,
583595
Info: info,
584596
Subs: subs,
585597
ExpireAt: expireAt,
586598
Meta: claims.Meta,
587599
}
588-
600+
if verifier.userIDClaim != "" {
601+
ct.UserID = claims.UserID
602+
} else {
603+
ct.UserID = claims.RegisteredClaims.Subject
604+
}
589605
return ct, nil
590606
}
591607

@@ -736,6 +752,11 @@ func (verifier *VerifierJWT) VerifySubscribeToken(t string, skipVerify bool) (Su
736752
Data: data,
737753
},
738754
}
755+
if verifier.userIDClaim != "" {
756+
st.UserID = claims.UserID
757+
} else {
758+
st.UserID = claims.RegisteredClaims.Subject
759+
}
739760
return st, nil
740761
}
741762

@@ -766,11 +787,11 @@ func (verifier *VerifierJWT) Reload(config VerifierConfig) error {
766787
return fmt.Errorf("error compiling issuer regex: %w", err)
767788
}
768789
}
769-
770790
verifier.algorithms = alg
771791
verifier.audience = config.Audience
772792
verifier.audienceRe = audienceRe
773793
verifier.issuer = config.Issuer
774794
verifier.issuerRe = issuerRe
795+
verifier.userIDClaim = config.UserIDClaim
775796
return nil
776797
}

0 commit comments

Comments
 (0)