Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
cb1fbb2
WIP add support for rfc7591, 7592
mqf20 Jul 22, 2025
03971db
WIP request
mqf20 Jul 22, 2025
5fdcb5d
WIP updated mock
mqf20 Jul 24, 2025
fc3b6cd
WIP added request and response structs
mqf20 Jul 25, 2025
9604978
WIP added TODO
mqf20 Jul 25, 2025
253ed4e
WIP added ClientUpdateRequest
mqf20 Jul 25, 2025
1ecae91
WIP updated to use ClientUpdateRequest
mqf20 Jul 25, 2025
b19bf9e
WIP added comments
mqf20 Jul 27, 2025
6713d71
WIP completed structure
mqf20 Jul 27, 2025
fa0684b
WIP completed example
mqf20 Jul 27, 2025
38de4e1
WIP added comments
mqf20 Jul 27, 2025
dd0f251
WIP added correct return code
mqf20 Jul 27, 2025
fe7a3d9
WIP updated example
mqf20 Jul 28, 2025
7970aa0
WIP fixed return code
mqf20 Jul 28, 2025
502f1cd
WIP fixed request/response structs
mqf20 Jul 28, 2025
fcd92dc
WIP improved request/response structs
mqf20 Aug 1, 2025
e9470de
WIP added placeholders for auth
mqf20 Aug 4, 2025
6db9c0c
Merge branch 'main' into feature/support-dynamic-registration-managem…
mqf20 Aug 4, 2025
79fc284
WIP refactored handlers
mqf20 Aug 5, 2025
1fb98cf
WIP improved interface
mqf20 Aug 5, 2025
843cc6b
WIP fixed errors in examples
mqf20 Aug 5, 2025
508facd
WIP added docs
mqf20 Aug 5, 2025
9fd4146
WIP refactored
mqf20 Aug 5, 2025
47cd6a2
WIP added docs
mqf20 Aug 5, 2025
f9488a3
WIP added docs
mqf20 Aug 5, 2025
435cfe7
WIP improved error checking for ClientMetadata UnmarshalJSON
mqf20 Aug 5, 2025
e7c521c
WIP improved error checking for ClientMetadata MarshalJSON
mqf20 Aug 5, 2025
d31d43f
WIP improved error checking for ClientMetadata MarshalJSON
mqf20 Aug 5, 2025
98ebfbc
WIP added return codes
mqf20 Aug 8, 2025
542d2c4
WIP fixed unit tests
mqf20 Aug 8, 2025
a58f16f
WIP fixed example
mqf20 Aug 8, 2025
5fd9975
WIP cleaned up checks
mqf20 Aug 9, 2025
19eb6af
WIP added InternationalizedField
mqf20 Aug 9, 2025
69a47d5
WIP added InternationalizedField
mqf20 Aug 9, 2025
7e0fa8b
WIP improved tests
mqf20 Aug 9, 2025
69ce55b
WIP added InternationalizedField
mqf20 Aug 10, 2025
1ccb1e8
WIP improved InternationalizedField
mqf20 Aug 10, 2025
82203a1
WIP improved parsing
mqf20 Aug 10, 2025
e72fd65
WIP updated to use internationalizedfield
mqf20 Aug 10, 2025
eae4492
WIP removed marshal/unmarshal
mqf20 Aug 10, 2025
3c979c6
WIP renamed, added fields
mqf20 Aug 10, 2025
4fba202
WIP added helpers
mqf20 Aug 10, 2025
6899c58
WIP fixed example
mqf20 Aug 10, 2025
f94ee46
WIP fixed handlers
mqf20 Aug 10, 2025
7d56320
Merge pull request #1 from mqf20/feature/support-dynamic-registration…
mqf20 Aug 10, 2025
edc677f
Merge branch 'main' into main
mqf20 Sep 28, 2025
282620c
Update pkg/oidc/discovery.go
mqf20 Sep 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions example/server/storage/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Client struct {
clockSkew time.Duration
postLogoutRedirectURIGlobs []string
redirectURIGlobs []string
registrationAccessToken string
}

// GetID must return the client_id
Expand Down
246 changes: 245 additions & 1 deletion example/server/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/rsa"
"errors"
"fmt"
"golang.org/x/text/language"
"math/big"
"strings"
"sync"
Expand All @@ -31,9 +32,10 @@ var serviceKey1 = &rsa.PublicKey{
var (
_ op.Storage = &Storage{}
_ op.ClientCredentialsStorage = &Storage{}
_ op.ClientsStorage = &Storage{}
)

// storage implements the op.Storage interface
// Storage implements the op.Storage interface
// typically you would implement this as a layer on top of your database
// for simplicity this example keeps everything in-memory
type Storage struct {
Expand Down Expand Up @@ -931,3 +933,245 @@ func (s *Storage) ClientCredentialsTokenRequest(ctx context.Context, clientID st
Scopes: scopes,
}, nil
}

func (s *Storage) RegisterClient(_ context.Context, c *oidc.ClientRegistrationRequest) (*oidc.ClientRegistrationResponse, error) {
s.lock.Lock()
defer s.lock.Unlock()
client := Client{
id: uuid.New().String(),
secret: uuid.New().String(),
redirectURIs: c.RedirectURIs,
applicationType: 0,
authMethod: c.TokenEndpointAuthMethod,
loginURL: nil,
responseTypes: c.ResponseTypes,
grantTypes: c.GrantTypes,
accessTokenType: 0,
devMode: false,
idTokenUserinfoClaimsAssertion: false,
clockSkew: 0,
postLogoutRedirectURIGlobs: nil,
redirectURIGlobs: nil,
registrationAccessToken: uuid.New().String(),
}
s.clients[client.id] = &client

return &oidc.ClientRegistrationResponse{
ClientInformationResponse: oidc.ClientInformationResponse{
ClientMetadata: oidc.ClientMetadata{
RedirectURIs: client.redirectURIs,
TokenEndpointAuthMethod: client.authMethod,
GrantTypes: client.grantTypes,
ResponseTypes: client.responseTypes,
ClientName: oidc.InternationalizedField{
FieldName: "client_name",
Entries: map[language.Tag]string{
language.Und: client.id,
},
},
//ClientURI: nil,
//LogoURI: nil,
//Scope: "",
//Contacts: nil,
//TOSURI: nil,
//PolicyURI: nil,
//JWKSURI: "",
//JWKS: jose.JSONWebKeySet{},
//SoftwareID: "",
//SoftwareVersion: "",
//ApplicationType: "",
//SectorIdentifierURI: "",
//SubjectType: "",
//IDTokenSignedResponseAlg: "",
//IDTokenEncryptedResponseAlg: "",
//IDTokenEncryptedResponseEnc: "",
//UserinfoSignedResponseAlg: "",
//UserinfoEncryptedResponseAlg: "",
//UserinfoEncryptedResponseEnc: "",
//RequestObjectSigningAlg: "",
//RequestObjectEncryptionAlg: "",
//RequestObjectEncryptionEnc: "",
//TokenEndpointAuthSigningAlg: "",
//DefaultMaxAge: 0,
//RequireAuthTime: false,
//DefaultACRValues: nil,
//InitiateLoginURI: "",
//RequestURIs: nil,
//PostLogoutRedirectURIs: nil,
//ExtraParameters: nil,
},
ClientID: client.id,
ClientSecret: client.secret,
//ClientIDIssuedAt: 0,
//ClientSecretExpiresAt: 0,
},
RegistrationAccessToken: client.registrationAccessToken,
RegistrationClientURI: "",
}, nil
}

func (s *Storage) ReadClient(_ context.Context, clientID string) (*oidc.ClientReadResponse, error) {
s.lock.Lock()
defer s.lock.Unlock()
client, ok := s.clients[clientID]
if !ok {
return nil, errors.New("client not found")
}
return &oidc.ClientReadResponse{
ClientRegistrationResponse: oidc.ClientRegistrationResponse{
ClientInformationResponse: oidc.ClientInformationResponse{
ClientMetadata: oidc.ClientMetadata{
RedirectURIs: client.redirectURIs,
TokenEndpointAuthMethod: client.authMethod,
GrantTypes: client.grantTypes,
ResponseTypes: client.responseTypes,
ClientName: oidc.InternationalizedField{
FieldName: "client_name",
Entries: map[language.Tag]string{
language.Und: client.id,
},
},
//ClientURI: nil,
//LogoURI: nil,
//Scope: "",
//Contacts: nil,
//TOSURI: nil,
//PolicyURI: nil,
//JWKSURI: "",
//JWKS: jose.JSONWebKeySet{},
//SoftwareID: "",
//SoftwareVersion: "",
//ApplicationType: "",
//SectorIdentifierURI: "",
//SubjectType: "",
//IDTokenSignedResponseAlg: "",
//IDTokenEncryptedResponseAlg: "",
//IDTokenEncryptedResponseEnc: "",
//UserinfoSignedResponseAlg: "",
//UserinfoEncryptedResponseAlg: "",
//UserinfoEncryptedResponseEnc: "",
//RequestObjectSigningAlg: "",
//RequestObjectEncryptionAlg: "",
//RequestObjectEncryptionEnc: "",
//TokenEndpointAuthSigningAlg: "",
//DefaultMaxAge: 0,
//RequireAuthTime: false,
//DefaultACRValues: nil,
//InitiateLoginURI: "",
//RequestURIs: nil,
//PostLogoutRedirectURIs: nil,
//ExtraParameters: nil,
},
ClientID: client.id,
ClientSecret: client.secret,
//ClientIDIssuedAt: 0,
//ClientSecretExpiresAt: 0,
},
RegistrationAccessToken: client.registrationAccessToken,
RegistrationClientURI: "",
},
}, nil
}

func (s *Storage) UpdateClient(_ context.Context, c *oidc.ClientUpdateRequest) (*oidc.ClientInformationResponse, error) {
s.lock.Lock()
defer s.lock.Unlock()
client, ok := s.clients[c.ClientID]
if !ok {
return nil, errors.New("client not found")
}
client.secret = c.ClientSecret
client.redirectURIs = c.RedirectURIs
client.authMethod = c.TokenEndpointAuthMethod
client.grantTypes = c.GrantTypes
client.responseTypes = c.ResponseTypes

return &oidc.ClientInformationResponse{
ClientMetadata: oidc.ClientMetadata{
RedirectURIs: client.redirectURIs,
TokenEndpointAuthMethod: client.authMethod,
GrantTypes: client.grantTypes,
ResponseTypes: client.responseTypes,
ClientName: oidc.InternationalizedField{
FieldName: "client_name",
Entries: map[language.Tag]string{
language.Und: client.id,
},
},
//ClientURI: nil,
//LogoURI: nil,
//Scope: "",
//Contacts: nil,
//TOSURI: nil,
//PolicyURI: nil,
//JWKSURI: "",
//JWKS: jose.JSONWebKeySet{},
//SoftwareID: "",
//SoftwareVersion: "",
//ApplicationType: "",
//SectorIdentifierURI: "",
//SubjectType: "",
//IDTokenSignedResponseAlg: "",
//IDTokenEncryptedResponseAlg: "",
//IDTokenEncryptedResponseEnc: "",
//UserinfoSignedResponseAlg: "",
//UserinfoEncryptedResponseAlg: "",
//UserinfoEncryptedResponseEnc: "",
//RequestObjectSigningAlg: "",
//RequestObjectEncryptionAlg: "",
//RequestObjectEncryptionEnc: "",
//TokenEndpointAuthSigningAlg: "",
//DefaultMaxAge: 0,
//RequireAuthTime: false,
//DefaultACRValues: nil,
//InitiateLoginURI: "",
//RequestURIs: nil,
//PostLogoutRedirectURIs: nil,
//ExtraParameters: nil,
},
ClientID: client.id,
ClientSecret: client.secret,
//ClientIDIssuedAt: 0,
//ClientSecretExpiresAt: 0,
}, nil
}

func (s *Storage) DeleteClient(_ context.Context, clientID string) error {
s.lock.Lock()
defer s.lock.Unlock()
// TODO(mqf20): If possible, the authorization server SHOULD immediately invalidate all existing authorization grants and currently active access tokens, all refresh tokens, and all other tokens associated with this client.
delete(s.clients, clientID)
return nil
}

func (s *Storage) AuthorizeClientRegistration(ctx context.Context, initialAccessToken string, c *oidc.ClientRegistrationRequest) error {
if initialAccessToken != "verysecure" {
return op.ErrInvalidInitialAccessToken
}
return nil
}

func (s *Storage) authorizeClient(clientID, registrationAccessToken string) error {
s.lock.Lock()
defer s.lock.Unlock()
c, ok := s.clients[clientID]
if !ok {
return op.ErrInvalidClient
}
if registrationAccessToken != c.registrationAccessToken {
return op.ErrInvalidRegistrationAccessToken
}
return nil
}

func (s *Storage) AuthorizeClientRead(ctx context.Context, clientID, registrationAccessToken string) error {
return s.authorizeClient(clientID, registrationAccessToken)
}

func (s *Storage) AuthorizeClientUpdate(ctx context.Context, clientID, registrationAccessToken string) error {
return s.authorizeClient(clientID, registrationAccessToken)
}

func (s *Storage) AuthorizeClientDelete(ctx context.Context, clientID, registrationAccessToken string) error {
return s.authorizeClient(clientID, registrationAccessToken)
}
6 changes: 6 additions & 0 deletions pkg/oidc/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ const (
PromptSelectAccount = "select_account"
)

var ResponseTypeMap = map[string]ResponseType{
string(ResponseTypeCode): ResponseTypeCode,
string(ResponseTypeIDToken): ResponseTypeIDToken,
string(ResponseTypeIDTokenOnly): ResponseTypeIDTokenOnly,
}

// AuthRequest according to:
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
type AuthRequest struct {
Expand Down
9 changes: 8 additions & 1 deletion pkg/oidc/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type DiscoveryConfiguration struct {
// It may also contain the OP's encryption keys that RPs can use to encrypt request to the OP.
JwksURI string `json:"jwks_uri,omitempty"`

// RegistrationEndpoint is the URL for the Dynamic Client Registration.
// RegistrationEndpoint is the URL for the Dynamic Client Registration (RFC7591, RFC7592).
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`

// ScopesSupported lists an array of supported scopes. This list must not include every supported scope by the OP.
Expand Down Expand Up @@ -167,3 +167,10 @@ const (
var AllAuthMethods = []AuthMethod{
AuthMethodBasic, AuthMethodPost, AuthMethodNone, AuthMethodPrivateKeyJWT,
}

var AuthMethodMap = map[string]AuthMethod{
string(AuthMethodBasic): AuthMethodBasic,
string(AuthMethodPost): AuthMethodPost,
string(AuthMethodNone): AuthMethodNone,
string(AuthMethodPrivateKeyJWT): AuthMethodPrivateKeyJWT,
}
Loading
Loading