From 363e9d522882ca6281e68ce87cf23566cce91398 Mon Sep 17 00:00:00 2001 From: Maksim Nabokikh Date: Fri, 20 Mar 2026 22:07:41 +0100 Subject: [PATCH] feat: use protobuf for session cookie (#4675) Signed-off-by: maksim.nabokikh --- server/internal/types.pb.go | 80 ++++++++++++++++++++++++++++++++++--- server/internal/types.proto | 7 ++++ server/session.go | 40 ++++++++----------- 3 files changed, 98 insertions(+), 29 deletions(-) diff --git a/server/internal/types.pb.go b/server/internal/types.pb.go index cabbea2e..8f9cbeb8 100644 --- a/server/internal/types.pb.go +++ b/server/internal/types.pb.go @@ -130,6 +130,67 @@ func (x *IDTokenSubject) GetConnId() string { return "" } +// SessionCookie holds session identity encoded into the session cookie value. +type SessionCookie struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ConnectorId string `protobuf:"bytes,2,opt,name=connector_id,json=connectorId,proto3" json:"connector_id,omitempty"` + Nonce string `protobuf:"bytes,3,opt,name=nonce,proto3" json:"nonce,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SessionCookie) Reset() { + *x = SessionCookie{} + mi := &file_server_internal_types_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SessionCookie) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionCookie) ProtoMessage() {} + +func (x *SessionCookie) ProtoReflect() protoreflect.Message { + mi := &file_server_internal_types_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SessionCookie.ProtoReflect.Descriptor instead. +func (*SessionCookie) Descriptor() ([]byte, []int) { + return file_server_internal_types_proto_rawDescGZIP(), []int{2} +} + +func (x *SessionCookie) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *SessionCookie) GetConnectorId() string { + if x != nil { + return x.ConnectorId + } + return "" +} + +func (x *SessionCookie) GetNonce() string { + if x != nil { + return x.Nonce + } + return "" +} + var File_server_internal_types_proto protoreflect.FileDescriptor var file_server_internal_types_proto_rawDesc = string([]byte{ @@ -144,10 +205,16 @@ var file_server_internal_types_proto_rawDesc = string([]byte{ 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x6e, 0x49, 0x64, - 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, - 0x65, 0x78, 0x69, 0x64, 0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x22, 0x61, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6f, 0x6b, 0x69, + 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, + 0x6e, 0x63, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x64, 0x65, 0x78, 0x69, 0x64, 0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -162,10 +229,11 @@ func file_server_internal_types_proto_rawDescGZIP() []byte { return file_server_internal_types_proto_rawDescData } -var file_server_internal_types_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_server_internal_types_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_server_internal_types_proto_goTypes = []any{ (*RefreshToken)(nil), // 0: internal.RefreshToken (*IDTokenSubject)(nil), // 1: internal.IDTokenSubject + (*SessionCookie)(nil), // 2: internal.SessionCookie } var file_server_internal_types_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -186,7 +254,7 @@ func file_server_internal_types_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_server_internal_types_proto_rawDesc), len(file_server_internal_types_proto_rawDesc)), NumEnums: 0, - NumMessages: 2, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/server/internal/types.proto b/server/internal/types.proto index 6a949fe8..324e8854 100644 --- a/server/internal/types.proto +++ b/server/internal/types.proto @@ -17,3 +17,10 @@ message IDTokenSubject { string user_id = 1; string conn_id = 2; } + +// SessionCookie holds session identity encoded into the session cookie value. +message SessionCookie { + string user_id = 1; + string connector_id = 2; + string nonce = 3; +} diff --git a/server/session.go b/server/session.go index 5f65ea00..52424c82 100644 --- a/server/session.go +++ b/server/session.go @@ -9,9 +9,9 @@ import ( "fmt" "net/http" "path" - "strings" "time" + "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/storage" ) @@ -32,33 +32,27 @@ func remoteIP(r *http.Request) string { return r.RemoteAddr } -// sessionCookieValue encodes session identity into a cookie value. -// Format: base64url(userID) + "." + base64url(connectorID) + "." + nonce -// TODO(nabokihms): consider cookie encoding +// sessionCookieValue encodes session identity into a cookie value using protobuf. func sessionCookieValue(userID, connectorID, nonce string) string { - return base64.RawURLEncoding.EncodeToString([]byte(userID)) + - "." + base64.RawURLEncoding.EncodeToString([]byte(connectorID)) + - "." + nonce + val, err := internal.Marshal(&internal.SessionCookie{ + UserId: userID, + ConnectorId: connectorID, + Nonce: nonce, + }) + if err != nil { + // Should never happen with valid string inputs. + panic(fmt.Sprintf("marshal session cookie: %v", err)) + } + return val } -// parseSessionCookie decodes a session cookie value into its components. +// parseSessionCookie decodes a protobuf-encoded session cookie value. func parseSessionCookie(value string) (userID, connectorID, nonce string, err error) { - parts := strings.SplitN(value, ".", 3) - if len(parts) != 3 { - return "", "", "", fmt.Errorf("invalid session cookie format") + var cookie internal.SessionCookie + if err := internal.Unmarshal(value, &cookie); err != nil { + return "", "", "", fmt.Errorf("decode session cookie: %w", err) } - - userIDBytes, err := base64.RawURLEncoding.DecodeString(parts[0]) - if err != nil { - return "", "", "", fmt.Errorf("decode userID: %w", err) - } - - connectorIDBytes, err := base64.RawURLEncoding.DecodeString(parts[1]) - if err != nil { - return "", "", "", fmt.Errorf("decode connectorID: %w", err) - } - - return string(userIDBytes), string(connectorIDBytes), parts[2], nil + return cookie.UserId, cookie.ConnectorId, cookie.Nonce, nil } func (s *Server) sessionCookiePath() string {