Skip to content

Commit 07b9810

Browse files
committed
add OpenID Connect discovery endpoint and update configuration handling
1 parent a61e8d3 commit 07b9810

File tree

4 files changed

+104
-10
lines changed

4 files changed

+104
-10
lines changed

README.md

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This mock server recreates Google's OAuth2 flow without requiring an internet co
1313
- `/authorize` - Authorization endpoint where users are redirected to authenticate
1414
- `/token` - Token exchange endpoint to obtain access tokens
1515
- `/userinfo` - User profile information endpoint
16+
- `/.well-known/openid-configuration` - OpenID Connect discovery endpoint
1617

1718
## Use Cases
1819
- Develop OAuth2 clients offline
@@ -47,8 +48,14 @@ go build -o mock-oauth2-server ./cmd/server
4748
# Specify a custom port using the command-line flag (highest priority)
4849
./mock-oauth2-server --port 9088
4950

51+
# Specify a custom hostname for the server URLs
52+
./mock-oauth2-server --host http://mock-oauth2-server:9088
53+
5054
# Specify a custom port using environment variable (used if no command-line flag is provided)
5155
MOCK_OAUTH_PORT=9088 ./mock-oauth2-server
56+
57+
# Specify a custom issuer URL using environment variable (useful in containerized environments)
58+
MOCK_ISSUER_URL=http://mock-oauth2:8080 ./mock-oauth2-server
5259
```
5360

5461
## Running with Docker
@@ -123,6 +130,7 @@ services:
123130
124131
- MOCK_USER_NAME=Test User
125132
- MOCK_TOKEN_EXPIRY=3600
133+
- MOCK_ISSUER_URL=http://mock-oauth2:8080
126134
# Mount a volume for custom fixtures if needed
127135
# volumes:
128136
# - ./test/fixtures:/app/test/fixtures
@@ -185,6 +193,8 @@ golang-mock-oauth2-server/
185193
│ │ ├── token_test.go # Test token handler
186194
│ │ ├── userinfo.go
187195
│ │ ├── userinfo_test.go # Test userinfo handler
196+
│ │ ├── openid_config.go # OpenID Connect discovery endpoint
197+
│ │ ├── openid_config_test.go # Test OpenID Connect discovery
188198
│ │ ├── config.go
189199
│ │ └── config_test.go # Test config handler
190200
│ ├── middleware/
@@ -227,6 +237,7 @@ Handler functions process incoming HTTP requests for each OAuth2 endpoint:
227237
- `authorize.go` - Handles user authentication and generates authorization codes
228238
- `token.go` - Exchanges authorization codes for access tokens
229239
- `userinfo.go` - Returns user profile information
240+
- `openid_config.go` - Provides OpenID Connect discovery metadata
230241
- `config.go` - Manages dynamic configuration for testing
231242

232243
#### Configuration (`internal/config/`)
@@ -312,6 +323,33 @@ Retrieves mock user profile information.
312323
}
313324
```
314325

326+
#### OpenID Connect Discovery Endpoint (`/.well-known/openid-configuration`)
327+
328+
Provides OpenID Connect (OIDC) configuration metadata for client auto-configuration.
329+
330+
**Method**: GET
331+
332+
**Response**: A JSON document with standard OIDC configuration
333+
334+
```json
335+
{
336+
"issuer": "http://localhost:8080",
337+
"authorization_endpoint": "http://localhost:8080/authorize",
338+
"token_endpoint": "http://localhost:8080/token",
339+
"userinfo_endpoint": "http://localhost:8080/userinfo",
340+
"jwks_uri": "http://localhost:8080/jwks",
341+
"response_types_supported": ["code"],
342+
"subject_types_supported": ["public"],
343+
"id_token_signing_alg_values_supported": ["RS256"],
344+
"scopes_supported": ["openid", "email", "profile"],
345+
"token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"],
346+
"claims_supported": [
347+
"sub", "iss", "name", "given_name",
348+
"family_name", "email", "email_verified", "picture"
349+
]
350+
}
351+
```
352+
315353
#### Configuration Endpoint
316354

317355
##### Dynamic Configuration Endpoint (`/config`)
@@ -376,36 +414,60 @@ Available configuration options:
376414
- Environment: `MOCK_OAUTH_PORT=9088`
377415
- Default: `8080`
378416

417+
- Issuer URL:
418+
- Command-line: `--host http://custom-hostname:9088`
419+
- Environment: `MOCK_ISSUER_URL=http://mock-oauth2:9088`
420+
- Default: `http://localhost:[port]`
421+
379422
- Other settings (environment variables only):
380423
- `MOCK_USER_EMAIL` - Email for the mock user (default: [email protected])
381424
- `MOCK_USER_NAME` - Name for the mock user (default: Test User)
382425
- `MOCK_TOKEN_EXPIRY` - Token expiry in seconds (default: 3600)
383426

427+
The issuer URL is particularly important in containerized environments where the service name differs from "localhost". It affects the URLs returned in the OpenID Connect discovery document and needs to match what your OAuth client is configured to use.
428+
384429
### Example Usage
385430

386431
In your OAuth2 client application:
387432

388-
```bash
433+
```go
434+
// For a Go application using the golang.org/x/oauth2 package
435+
import (
436+
"context"
437+
"golang.org/x/oauth2"
438+
)
439+
389440
const (
390441
clientID = "test-client-id"
391442
clientSecret = "test-client-secret"
392443
redirectURL = "http://localhost:8081/callback"
393-
authURL = "http://localhost:8080/authorize"
394-
tokenURL = "http://localhost:8080/token"
395-
userInfoURL = "http://localhost:8080/userinfo"
396444
)
397445

398-
// Configure OAuth2 client to use the mock server
446+
// You can either specify endpoints manually:
399447
oauth2Config := &oauth2.Config{
400448
ClientID: clientID,
401449
ClientSecret: clientSecret,
402450
RedirectURL: redirectURL,
403451
Scopes: []string{"openid", "email", "profile"},
404452
Endpoint: oauth2.Endpoint{
405-
AuthURL: authURL,
406-
TokenURL: tokenURL,
453+
AuthURL: "http://localhost:8080/authorize",
454+
TokenURL: "http://localhost:8080/token",
407455
},
408456
}
457+
458+
// Or use the OpenID Connect discovery document:
459+
provider, err := oidc.NewProvider(context.Background(), "http://localhost:8080")
460+
if err != nil {
461+
// handle error
462+
}
463+
464+
oauth2Config := &oauth2.Config{
465+
ClientID: clientID,
466+
ClientSecret: clientSecret,
467+
RedirectURL: redirectURL,
468+
Scopes: []string{"openid", "email", "profile"},
469+
Endpoint: provider.Endpoint(),
470+
}
409471
```
410472

411473
## Testing Strategies

cmd/server/main.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,19 @@ func main() {
4646

4747
// Determine the base URL for OpenID Connect configuration
4848
baseURL := host
49+
50+
// If host flag is not provided, check for IssuerURL in config (from MOCK_ISSUER_URL env var)
51+
if baseURL == "" {
52+
baseURL = cfg.IssuerURL
53+
}
54+
55+
// If neither host flag nor MOCK_ISSUER_URL env var is provided, use localhost
4956
if baseURL == "" {
5057
baseURL = fmt.Sprintf("http://localhost:%d", serverPort)
5158
}
5259

60+
log.Printf("Using issuer URL: %s", baseURL)
61+
5362
// Initialize in-memory store with configuration
5463
memoryStore := store.NewMemoryStore()
5564

@@ -65,7 +74,7 @@ func main() {
6574
mux.Handle("/userinfo", &handlers.UserInfoHandler{Store: memoryStore})
6675
mux.Handle("/config", handlers.NewConfigHandler(memoryStore, defaultUser))
6776
mux.Handle("/version", handlers.NewVersionHandler())
68-
77+
6978
// Add OpenID Connect Discovery endpoint
7079
mux.Handle("/.well-known/openid-configuration", handlers.NewOpenIDConfigHandler(baseURL))
7180

internal/config/config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type ServerConfig struct {
1212
MockUserEmail string
1313
MockUserName string
1414
MockTokenExpiry int
15+
IssuerURL string
1516
mu sync.RWMutex
1617
}
1718

@@ -20,6 +21,7 @@ var defaultConfig = ServerConfig{
2021
MockUserEmail: "[email protected]",
2122
MockUserName: "Test User",
2223
MockTokenExpiry: 3600,
24+
IssuerURL: "", // Will be auto-generated if not specified
2325
}
2426

2527
// LoadConfig loads server configuration from environment variables or returns defaults
@@ -46,6 +48,11 @@ func LoadConfig() *ServerConfig {
4648
}
4749
}
4850

51+
// Load issuer URL from environment variable
52+
if issuerURL, exists := os.LookupEnv("MOCK_ISSUER_URL"); exists {
53+
config.IssuerURL = issuerURL
54+
}
55+
4956
return config
5057
}
5158

@@ -66,6 +73,9 @@ func (c *ServerConfig) UpdateConfig(newConfig map[string]interface{}) {
6673
if expiry, ok := newConfig["mock_token_expiry"].(int); ok {
6774
c.MockTokenExpiry = expiry
6875
}
76+
if issuerURL, ok := newConfig["issuer_url"].(string); ok {
77+
c.IssuerURL = issuerURL
78+
}
6979
}
7080

7181
// GetConfig returns a copy of the current server configuration without the mutex
@@ -78,5 +88,6 @@ func (c *ServerConfig) GetConfig() ServerConfig {
7888
MockUserEmail: c.MockUserEmail,
7989
MockUserName: c.MockUserName,
8090
MockTokenExpiry: c.MockTokenExpiry,
91+
IssuerURL: c.IssuerURL,
8192
}
8293
}

internal/config/config_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ func TestLoadConfig(t *testing.T) {
1111
os.Setenv("MOCK_USER_EMAIL", "[email protected]")
1212
os.Setenv("MOCK_USER_NAME", "Test User")
1313
os.Setenv("MOCK_TOKEN_EXPIRY", "7200")
14+
os.Setenv("MOCK_ISSUER_URL", "http://mock-oauth2:9090")
1415
defer os.Unsetenv("MOCK_OAUTH_PORT")
1516
defer os.Unsetenv("MOCK_USER_EMAIL")
1617
defer os.Unsetenv("MOCK_USER_NAME")
1718
defer os.Unsetenv("MOCK_TOKEN_EXPIRY")
19+
defer os.Unsetenv("MOCK_ISSUER_URL")
1820

1921
config := LoadConfig()
2022

@@ -30,6 +32,9 @@ func TestLoadConfig(t *testing.T) {
3032
if config.MockTokenExpiry != 7200 {
3133
t.Errorf("expected MockTokenExpiry to be 7200, got %d", config.MockTokenExpiry)
3234
}
35+
if config.IssuerURL != "http://mock-oauth2:9090" {
36+
t.Errorf("expected IssuerURL to be 'http://mock-oauth2:9090', got '%s'", config.IssuerURL)
37+
}
3338
}
3439

3540
func TestUpdateConfig(t *testing.T) {
@@ -40,6 +45,7 @@ func TestUpdateConfig(t *testing.T) {
4045
"mock_user_email": "[email protected]",
4146
"mock_user_name": "Updated User",
4247
"mock_token_expiry": 3600,
48+
"issuer_url": "http://updated-mock-oauth2:8081",
4349
}
4450

4551
config.UpdateConfig(newConfig)
@@ -56,6 +62,9 @@ func TestUpdateConfig(t *testing.T) {
5662
if config.MockTokenExpiry != 3600 {
5763
t.Errorf("expected MockTokenExpiry to be 3600, got %d", config.MockTokenExpiry)
5864
}
65+
if config.IssuerURL != "http://updated-mock-oauth2:8081" {
66+
t.Errorf("expected IssuerURL to be 'http://updated-mock-oauth2:8081', got '%s'", config.IssuerURL)
67+
}
5968
}
6069

6170
func TestGetConfig(t *testing.T) {
@@ -66,6 +75,7 @@ func TestGetConfig(t *testing.T) {
6675
"mock_user_email": "[email protected]",
6776
"mock_user_name": "GetConfig User",
6877
"mock_token_expiry": 1800,
78+
"issuer_url": "http://getconfig-mock-oauth2:8082",
6979
}
7080

7181
config.UpdateConfig(newConfig)
@@ -74,8 +84,10 @@ func TestGetConfig(t *testing.T) {
7484
if retrievedConfig.Port != 8082 ||
7585
retrievedConfig.MockUserEmail != "[email protected]" ||
7686
retrievedConfig.MockUserName != "GetConfig User" ||
77-
retrievedConfig.MockTokenExpiry != 1800 {
87+
retrievedConfig.MockTokenExpiry != 1800 ||
88+
retrievedConfig.IssuerURL != "http://getconfig-mock-oauth2:8082" {
7889

79-
t.Errorf("expected retrievedConfig to match updated config, got Port: %d, MockUserEmail: %s, MockUserName: %s, MockTokenExpiry: %d", retrievedConfig.Port, retrievedConfig.MockUserEmail, retrievedConfig.MockUserName, retrievedConfig.MockTokenExpiry)
90+
t.Errorf("expected retrievedConfig to match updated config, got Port: %d, MockUserEmail: %s, MockUserName: %s, MockTokenExpiry: %d, IssuerURL: %s",
91+
retrievedConfig.Port, retrievedConfig.MockUserEmail, retrievedConfig.MockUserName, retrievedConfig.MockTokenExpiry, retrievedConfig.IssuerURL)
8092
}
8193
}

0 commit comments

Comments
 (0)