Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions cmd/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/auth/generic"
"github.com/googleapis/genai-toolbox/internal/server"
)

Expand Down Expand Up @@ -309,6 +310,18 @@ func mergeConfigs(files ...Config) (Config, error) {
return Config{}, fmt.Errorf("resource conflicts detected:\n - %s\n\nPlease ensure each source, authService, tool, toolset and prompt has a unique name across all files", strings.Join(conflicts, "\n - "))
}

// Ensure only one authService has mcpEnabled = true
var mcpEnabledAuthServers []string
for name, authService := range merged.AuthServices {
// Only generic type has McpEnabled right now
if genericService, ok := authService.(generic.Config); ok && genericService.McpEnabled {
mcpEnabledAuthServers = append(mcpEnabledAuthServers, name)
}
}
if len(mcpEnabledAuthServers) > 1 {
return Config{}, fmt.Errorf("multiple authServices with mcpEnabled=true detected: %s. Only one MCP authorization server is currently supported", strings.Join(mcpEnabledAuthServers, ", "))
}

return merged, nil
}

Expand Down
102 changes: 68 additions & 34 deletions cmd/internal/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/auth/generic"
"github.com/googleapis/genai-toolbox/internal/auth/google"
"github.com/googleapis/genai-toolbox/internal/embeddingmodels/gemini"
"github.com/googleapis/genai-toolbox/internal/prebuiltconfigs"
Expand Down Expand Up @@ -616,38 +617,48 @@ func TestParseConfig(t *testing.T) {
type: google
clientId: testing-id
---
kind: embeddingModel
name: gemini-model
type: gemini
model: gemini-embedding-001
apiKey: some-key
dimension: 768
kind: authService
name: my-generic-auth
type: generic
audience: testings
authorizationServer: https://testings
mcpEnabled: true
scopesRequired:
- read:files
- write:files
---
kind: tool
name: example_tool
type: postgres-sql
source: my-pg-instance
description: some description
statement: |
SELECT * FROM SQL_STATEMENT;
parameters:
- name: country
type: string
description: some description
kind: embeddingModel
name: gemini-model
type: gemini
model: gemini-embedding-001
apiKey: some-key
dimension: 768
---
kind: toolset
name: example_toolset
tools:
- example_tool
kind: tool
name: example_tool
type: postgres-sql
source: my-pg-instance
description: some description
statement: |
SELECT * FROM SQL_STATEMENT;
parameters:
- name: country
type: string
description: some description
---
kind: prompt
name: code_review
description: ask llm to analyze code quality
messages:
- content: "please review the following code for quality: {{.code}}"
arguments:
- name: code
description: the code to review
kind: toolset
name: example_toolset
tools:
- example_tool
---
kind: prompt
name: code_review
description: ask llm to analyze code quality
messages:
- content: "please review the following code for quality: {{.code}}"
arguments:
- name: code
description: the code to review
`,
wantConfig: Config{
Sources: server.SourceConfigs{
Expand All @@ -669,6 +680,14 @@ func TestParseConfig(t *testing.T) {
Type: google.AuthServiceType,
ClientID: "testing-id",
},
"my-generic-auth": generic.Config{
Name: "my-generic-auth",
Type: generic.AuthServiceType,
Audience: "testings",
McpEnabled: true,
AuthorizationServer: "https://testings",
ScopesRequired: []string{"read:files", "write:files"},
},
},
EmbeddingModels: server.EmbeddingModelConfigs{
"gemini-model": gemini.Config{
Expand Down Expand Up @@ -2029,12 +2048,19 @@ func TestMergeConfigs(t *testing.T) {
Sources: server.SourceConfigs{"source1": httpsrc.Config{Name: "source1"}},
Tools: server.ToolConfigs{"tool2": http.Config{Name: "tool2"}},
}
fileMcp1 := Config{
AuthServices: server.AuthServiceConfigs{"generic1": generic.Config{Name: "generic1", McpEnabled: true}},
}
fileMcp2 := Config{
AuthServices: server.AuthServiceConfigs{"generic2": generic.Config{Name: "generic2", McpEnabled: true}},
}

testCases := []struct {
name string
files []Config
want Config
wantErr bool
name string
files []Config
want Config
wantErr bool
errString string
}{
{
name: "merge two distinct files",
Expand All @@ -2054,6 +2080,12 @@ func TestMergeConfigs(t *testing.T) {
files: []Config{file1, file2, fileWithConflicts},
wantErr: true,
},
{
name: "merge multiple mcp enabled generic",
files: []Config{fileMcp1, fileMcp2},
wantErr: true,
errString: "multiple authServices with mcpEnabled=true detected",
},
{
name: "merge single file",
files: []Config{file1},
Expand Down Expand Up @@ -2094,7 +2126,9 @@ func TestMergeConfigs(t *testing.T) {
if err == nil {
t.Fatal("expected an error for conflicting files but got none")
}
if !strings.Contains(err.Error(), "resource conflicts detected") {
if tc.errString != "" && !strings.Contains(err.Error(), tc.errString) {
t.Errorf("expected error %q, but got: %v", tc.errString, err)
} else if tc.errString == "" && !strings.Contains(err.Error(), "resource conflicts detected") {
t.Errorf("expected conflict error, but got: %v", err)
}
}
Expand Down
67 changes: 67 additions & 0 deletions docs/en/resources/authServices/generic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: "Generic OIDC Auth"
type: docs
weight: 2
description: >
Use a Generic OpenID Connect (OIDC) provider for OAuth 2.0 flow and token
lifecycle.
---

## Getting Started

The Generic Auth Service allows you to integrate with any OpenID Connect (OIDC)
compliant identity provider (IDP). It discovers the JWKS (JSON Web Key Set) URL
either through the provider's `/.well-known/openid-configuration` endpoint or
directly via the provided `authorizationServer`.

To configure this auth service, you need to provide the `audience` (typically
your client ID or the intended audience for the token), the
`authorizationServer` of your identity provider, and optionally a list of
`scopesRequired` that must be present in the token's claims.

## Behavior

### Token Validation

When a request is received, the service will:

1. Extract the token from the `<name>_token` header (e.g.,
`my-generic-auth_token`).
2. Fetch the JWKS from the configured `authorizationServer` (caching it in the
background) to verify the token's signature.
3. Validate that the token is not expired and its signature is valid.
4. Verify that the `aud` (audience) claim matches the configured `audience`.
claim contains all required scopes.
5. Return the validated claims to be used for [Authenticated
Parameters][auth-params] or [Authorized Invocations][auth-invoke].

[auth-invoke]: ../tools/#authorized-invocations
[auth-params]: ../tools/#authenticated-parameters

## Example

```yaml
kind: authServices
name: my-generic-auth
type: generic
audience: ${YOUR_OIDC_AUDIENCE}
authorizationServer: https://your-idp.example.com
mcpEnabled: false
scopesRequired:
- read
- write
```

{{< notice tip >}} Use environment variable replacement with the format
${ENV_NAME} instead of hardcoding your secrets into the configuration file.
{{< /notice >}}

## Reference

| **field** | **type** | **required** | **description** |
| ------------------- | :------: | :----------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | string | true | Must be "generic". |
| audience | string | true | The expected audience (`aud` claim) in the JWT token. This ensures the token was minted specifically for your application. |
| authorizationServer | string | true | The base URL of your OIDC provider. The service will append `/.well-known/openid-configuration` to discover the JWKS URI. HTTP is allowed but logs a warning. |
| mcpEnabled | bool | false | Indicates if MCP endpoint authentication should be applied. Defaults to false. |
| scopesRequired | []string | false | A list of required scopes that must be present in the token's `scope` claim to be considered valid. |
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ require (
github.com/ClickHouse/clickhouse-go/v2 v2.43.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.31.0
github.com/MicahParks/jwkset v0.11.0
github.com/MicahParks/keyfunc/v3 v3.8.0
github.com/apache/cassandra-gocql-driver/v2 v2.0.0
github.com/cenkalti/backoff/v5 v5.0.3
github.com/cockroachdb/cockroach-go/v2 v2.4.3
Expand All @@ -36,6 +38,7 @@ require (
github.com/go-sql-driver/mysql v1.9.3
github.com/goccy/go-yaml v1.19.2
github.com/godror/godror v0.50.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.9.1
Expand Down Expand Up @@ -172,7 +175,6 @@ require (
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/godror/knownpb v0.3.0 // indirect
github.com/gofrs/flock v0.13.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/MicahParks/jwkset v0.11.0 h1:yc0zG+jCvZpWgFDFmvs8/8jqqVBG9oyIbmBtmjOhoyQ=
github.com/MicahParks/jwkset v0.11.0/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0=
github.com/MicahParks/keyfunc/v3 v3.8.0 h1:Hx2dgIjAXGk9slakM6rV9BOeaWDPEXXZ4Us8guNBfds=
github.com/MicahParks/keyfunc/v3 v3.8.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
Expand Down
Loading
Loading