Add OAuth2 Client ID Metadata Document support

MCP connectors such as ChatGPT and Claude register via HTTPS
client_id URLs instead of pre-provisioned GIDs. Fetch and cache
their metadata documents, upsert clients on first use, and
advertise CIMD in OIDC discovery when allowed URLs are configured.

Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-06-19 16:35:53 +02:00
parent 9e6f1b9e8f
commit 5b0d3e5052
22 changed files with 1836 additions and 26 deletions

View File

@@ -27,7 +27,6 @@ import (
"go.probo.inc/probo/pkg/baseurl"
"go.probo.inc/probo/pkg/bearertoken"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/iam/oauth2"
"go.probo.inc/probo/pkg/securecookie"
@@ -167,7 +166,7 @@ func (h *OAuth2Handler) AuthorizeHandler(w http.ResponseWriter, r *http.Request)
IdentityID: identity.ID,
SessionID: session.ID,
ResponseType: in.ResponseType,
ClientID: in.ClientID,
ClientIDRaw: in.ClientIDRaw,
RedirectURI: in.RedirectURI,
Scopes: in.Scopes,
CodeChallenge: in.CodeChallenge,
@@ -445,12 +444,7 @@ func (h *OAuth2Handler) authenticateClient(r *http.Request) (*coredata.OAuth2Cli
return nil, oauth2.ErrInvalidClient
}
clientID, err := gid.ParseGID(clientIDStr)
if err != nil {
return nil, oauth2.ErrInvalidClient
}
return h.iam.OAuth2ServerService.AuthenticateClient(r.Context(), clientID, clientSecret)
return h.iam.OAuth2ServerService.AuthenticateClient(r.Context(), clientIDStr, clientSecret)
}
func (h *OAuth2Handler) handleAuthorizationCodeGrant(w http.ResponseWriter, r *http.Request) {

View File

@@ -50,7 +50,7 @@ func parseScopes(s string) (coredata.OAuth2Scopes, error) {
type (
OAuth2AuthorizeInput struct {
ClientID gid.GID
ClientIDRaw string
RedirectURI string
State string
ResponseType coredata.OAuth2ResponseType
@@ -112,9 +112,9 @@ type (
func (in *OAuth2AuthorizeInput) DecodeQuery(q url.Values) error {
var err error
in.ClientID, err = requireGID(q, "client_id")
if err != nil {
return err
in.ClientIDRaw = q.Get("client_id")
if in.ClientIDRaw == "" {
return fmt.Errorf("missing client_id")
}
in.RedirectURI = q.Get("redirect_uri")