diff --git a/server/handlers.go b/server/handlers.go index 62f1650c06..2fe9cd0600 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -889,6 +889,8 @@ func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) { s.withClientFromStorage(w, r, s.handlePasswordGrant) case grantTypeTokenExchange: s.withClientFromStorage(w, r, s.handleTokenExchange) + case grantTypeClientCredentials: + s.withClientFromStorage(w, r, s.handleClientCredentialsGrant) default: s.tokenErrHelper(w, errUnsupportedGrantType, "", http.StatusBadRequest) } @@ -1143,6 +1145,35 @@ func (s *Server) handleUserInfo(w http.ResponseWriter, r *http.Request) { w.Write(claims) } +func (s *Server) handleClientCredentialsGrant(w http.ResponseWriter, r *http.Request, client storage.Client) { + if err := r.ParseForm(); err != nil { + s.tokenErrHelper(w, errInvalidRequest, "Couldn't parse data", http.StatusBadRequest) + return + } + q := r.Form + + nonce := q.Get("nonce") + scopes := strings.Fields(q.Get("scope")) + + claims := storage.Claims{UserID: client.ID} + + accessToken, _, err := s.newAccessToken(r.Context(), client.ID, claims, scopes, nonce, "client") + if err != nil { + s.logger.ErrorContext(r.Context(), "failed to create new access token", "err", err) + s.tokenErrHelper(w, errServerError, err.Error(), http.StatusInternalServerError) + return + } + + idToken, expiry, err := s.newIDToken(r.Context(), client.ID, claims, scopes, nonce, accessToken, "", "client") + if err != nil { + s.tokenErrHelper(w, errServerError, fmt.Sprintf("failed to create ID token: %v", err), http.StatusInternalServerError) + return + } + + resp := s.toAccessTokenResponse(idToken, accessToken, "", expiry) + s.writeAccessToken(w, resp) +} + func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, client storage.Client) { ctx := r.Context() // Parse the fields diff --git a/server/handlers_test.go b/server/handlers_test.go index 0514d85c80..1ccfc2639f 100644 --- a/server/handlers_test.go +++ b/server/handlers_test.go @@ -62,6 +62,7 @@ func TestHandleDiscovery(t *testing.T) { Introspect: fmt.Sprintf("%s/token/introspect", httpServer.URL), GrantTypes: []string{ "authorization_code", + "client_credentials", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:token-exchange", diff --git a/server/oauth2.go b/server/oauth2.go index 5821f0ffd1..8fe754755d 100644 --- a/server/oauth2.go +++ b/server/oauth2.go @@ -143,6 +143,7 @@ const ( grantTypePassword = "password" grantTypeDeviceCode = "urn:ietf:params:oauth:grant-type:device_code" grantTypeTokenExchange = "urn:ietf:params:oauth:grant-type:token-exchange" + grantTypeClientCredentials = "client_credentials" ) const ( diff --git a/server/server.go b/server/server.go index e923e3e0cf..a233a9efd0 100644 --- a/server/server.go +++ b/server/server.go @@ -233,6 +233,7 @@ func newServer(ctx context.Context, c Config) (*Server, error) { grantTypeRefreshToken: true, grantTypeDeviceCode: true, grantTypeTokenExchange: true, + grantTypeClientCredentials: true, } supportedRes := make(map[string]bool) diff --git a/server/server_test.go b/server/server_test.go index 5a735f1d33..e0a0c7729f 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -107,6 +107,7 @@ func newTestServer(t *testing.T, updateConfig func(c *Config)) (*httptest.Server grantTypeTokenExchange, grantTypeImplicit, grantTypePassword, + grantTypeClientCredentials, }, Signer: sig, } @@ -1774,7 +1775,7 @@ func TestServerSupportedGrants(t *testing.T) { { name: "Simple", config: func(c *Config) {}, - resGrants: []string{grantTypeAuthorizationCode, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, + resGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, }, { name: "Minimal", @@ -1784,12 +1785,12 @@ func TestServerSupportedGrants(t *testing.T) { { name: "With password connector", config: func(c *Config) { c.PasswordConnector = "local" }, - resGrants: []string{grantTypeAuthorizationCode, grantTypePassword, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, + resGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypePassword, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, }, { name: "With token response", config: func(c *Config) { c.SupportedResponseTypes = append(c.SupportedResponseTypes, responseTypeToken) }, - resGrants: []string{grantTypeAuthorizationCode, grantTypeImplicit, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, + resGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypeImplicit, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, }, { name: "All", @@ -1797,7 +1798,7 @@ func TestServerSupportedGrants(t *testing.T) { c.PasswordConnector = "local" c.SupportedResponseTypes = append(c.SupportedResponseTypes, responseTypeToken) }, - resGrants: []string{grantTypeAuthorizationCode, grantTypeImplicit, grantTypePassword, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, + resGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypeImplicit, grantTypePassword, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, }, }