Files
coder/coderd/oauth2_metadata_test.go
Thomas Kosiewski 33bbf18a4b feat: add OAuth2 protected resource metadata endpoint for RFC 9728 (#18643)
# Add OAuth2 Protected Resource Metadata Endpoint

This PR implements the OAuth2 Protected Resource Metadata endpoint according to RFC 9728. The endpoint is available at `/.well-known/oauth-protected-resource` and provides information about Coder as an OAuth2 protected resource.

Key changes:
- Added a new endpoint at `/.well-known/oauth-protected-resource` that returns metadata about Coder as an OAuth2 protected resource
- Created a new `OAuth2ProtectedResourceMetadata` struct in the SDK
- Added tests to verify the endpoint functionality
- Updated API documentation to include the new endpoint

The implementation currently returns basic metadata including the resource identifier and authorization server URL. The `scopes_supported` field is empty until a scope system based on RBAC permissions is implemented. The `bearer_methods_supported` field is omitted as Coder uses custom authentication methods rather than standard RFC 6750 bearer tokens.

A TODO has been added to implement RFC 6750 bearer token support in the future.
2025-07-02 18:58:41 +02:00

87 lines
2.8 KiB
Go

package coderd_test
import (
"context"
"encoding/json"
"net/http"
"net/url"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)
func TestOAuth2AuthorizationServerMetadata(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
serverURL := client.URL
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
// Use a plain HTTP client since this endpoint doesn't require authentication
endpoint := serverURL.ResolveReference(&url.URL{Path: "/.well-known/oauth-authorization-server"}).String()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
var metadata codersdk.OAuth2AuthorizationServerMetadata
err = json.NewDecoder(resp.Body).Decode(&metadata)
require.NoError(t, err)
// Verify the metadata
require.NotEmpty(t, metadata.Issuer)
require.NotEmpty(t, metadata.AuthorizationEndpoint)
require.NotEmpty(t, metadata.TokenEndpoint)
require.Contains(t, metadata.ResponseTypesSupported, "code")
require.Contains(t, metadata.GrantTypesSupported, "authorization_code")
require.Contains(t, metadata.GrantTypesSupported, "refresh_token")
require.Contains(t, metadata.CodeChallengeMethodsSupported, "S256")
}
func TestOAuth2ProtectedResourceMetadata(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
serverURL := client.URL
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
// Use a plain HTTP client since this endpoint doesn't require authentication
endpoint := serverURL.ResolveReference(&url.URL{Path: "/.well-known/oauth-protected-resource"}).String()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
var metadata codersdk.OAuth2ProtectedResourceMetadata
err = json.NewDecoder(resp.Body).Decode(&metadata)
require.NoError(t, err)
// Verify the metadata
require.NotEmpty(t, metadata.Resource)
require.NotEmpty(t, metadata.AuthorizationServers)
require.Len(t, metadata.AuthorizationServers, 1)
require.Equal(t, metadata.Resource, metadata.AuthorizationServers[0])
// BearerMethodsSupported is omitted since Coder uses custom authentication methods
// Standard RFC 6750 bearer tokens are not supported
require.True(t, len(metadata.BearerMethodsSupported) == 0)
// ScopesSupported can be empty until scope system is implemented
// Empty slice is marshaled as empty array, but can be nil when unmarshaled
require.True(t, len(metadata.ScopesSupported) == 0)
}