Skip to content

Commit 038871e

Browse files
feat(client): add support for short-lived tokens (#799)
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
1 parent 6ef5b20 commit 038871e

File tree

18 files changed

+2003
-2
lines changed

18 files changed

+2003
-2
lines changed

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 139
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-dd99495ad509338e6de862802839360dfe394d5cd6d6ba6d13fec8fca92328b8.yml
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-a6eca1bd01e0c434af356fe5275c206057216a4e626d1051d294c27016cd6d05.yml
33
openapi_spec_hash: 68abda9122013a9ae3f084cfdbe8e8c1
4-
config_hash: 5635033cdc8c930255f8b529a78de722
4+
config_hash: 4975e16a94e8f9901428022044131888

README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,104 @@ You may also replace the default `http.Client` with
953953
accepted (this overwrites any previous client) and receives requests after any
954954
middleware has been applied.
955955

956+
## Workload Identity Authentication
957+
958+
For cloud workloads (Kubernetes, Azure, Google Cloud Platform), you can use workload identity authentication instead of API keys. This provides short-lived tokens that are automatically refreshed.
959+
960+
### Kubernetes
961+
962+
```go
963+
import (
964+
"github.com/openai/openai-go/v3"
965+
"github.com/openai/openai-go/v3/auth"
966+
"github.com/openai/openai-go/v3/option"
967+
)
968+
969+
client := openai.NewClient(
970+
option.WithWorkloadIdentity(auth.WorkloadIdentity{
971+
ClientID: "your-client-id",
972+
IdentityProviderID: "idp-123",
973+
ServiceAccountID: "sa-456",
974+
Provider: auth.K8sServiceAccountTokenProvider(""),
975+
}),
976+
)
977+
```
978+
979+
### Azure Managed Identity
980+
981+
```go
982+
client := openai.NewClient(
983+
option.WithWorkloadIdentity(auth.WorkloadIdentity{
984+
ClientID: "your-client-id",
985+
IdentityProviderID: "idp-123",
986+
ServiceAccountID: "sa-456",
987+
Provider: auth.AzureManagedIdentityTokenProvider(nil),
988+
}),
989+
)
990+
```
991+
992+
### Google Cloud Compute Engine
993+
994+
```go
995+
client := openai.NewClient(
996+
option.WithWorkloadIdentity(auth.WorkloadIdentity{
997+
ClientID: "your-client-id",
998+
IdentityProviderID: "idp-123",
999+
ServiceAccountID: "sa-456",
1000+
Provider: auth.GCPIDTokenProvider(nil),
1001+
}),
1002+
)
1003+
```
1004+
1005+
### Custom Subject Token Provider
1006+
1007+
You can implement your own subject token provider:
1008+
1009+
```go
1010+
import (
1011+
"context"
1012+
1013+
"github.com/openai/openai-go/v3"
1014+
"github.com/openai/openai-go/v3/auth"
1015+
"github.com/openai/openai-go/v3/option"
1016+
)
1017+
1018+
type customTokenProvider struct{}
1019+
1020+
func (p *customTokenProvider) TokenType() auth.SubjectTokenType {
1021+
return auth.SubjectTokenTypeJWT
1022+
}
1023+
1024+
func (p *customTokenProvider) GetToken(ctx context.Context, httpClient auth.HTTPDoer) (string, error) {
1025+
return "your-token", nil
1026+
}
1027+
1028+
client := openai.NewClient(
1029+
option.WithWorkloadIdentity(auth.WorkloadIdentity{
1030+
ClientID: "your-client-id",
1031+
IdentityProviderID: "idp-123",
1032+
ServiceAccountID: "sa-456",
1033+
Provider: &customTokenProvider{},
1034+
}),
1035+
)
1036+
```
1037+
1038+
### Customizing Refresh Buffer
1039+
1040+
By default, tokens are refreshed 20 minutes (1200 seconds) before expiry. You can customize this:
1041+
1042+
```go
1043+
client := openai.NewClient(
1044+
option.WithWorkloadIdentity(auth.WorkloadIdentity{
1045+
ClientID: "your-client-id",
1046+
IdentityProviderID: "idp-123",
1047+
ServiceAccountID: "sa-456",
1048+
Provider: auth.K8sServiceAccountTokenProvider(""),
1049+
RefreshBufferSeconds: 600,
1050+
}),
1051+
)
1052+
```
1053+
9561054
## Microsoft Azure OpenAI
9571055

9581056
To use this library with [Azure OpenAI]https://learn.microsoft.com/azure/ai-services/openai/overview),

aliases.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,15 @@ type FunctionParameters = shared.FunctionParameters
448448
// This is an alias to an internal type.
449449
type Metadata = shared.Metadata
450450

451+
// This is an alias to an internal type.
452+
type OAuthErrorCode = shared.OAuthErrorCode
453+
454+
// Equals "invalid_grant"
455+
const OAuthErrorCodeInvalidGrant = shared.OAuthErrorCodeInvalidGrant
456+
457+
// Equals "invalid_subject_token"
458+
const OAuthErrorCodeInvalidSubjectToken = shared.OAuthErrorCodeInvalidSubjectToken
459+
451460
// **gpt-5 and o-series models only**
452461
//
453462
// Configuration options for

api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#FunctionDefinitionParam">FunctionDefinitionParam</a>
88
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#FunctionParameters">FunctionParameters</a>
99
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#Metadata">Metadata</a>
10+
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#OAuthErrorCode">OAuthErrorCode</a>
1011
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#ReasoningParam">ReasoningParam</a>
1112
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#ReasoningEffort">ReasoningEffort</a>
1213
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#ResponseFormatJSONObjectParam">ResponseFormatJSONObjectParam</a>

auth/error.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package auth
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/openai/openai-go/v3/shared"
7+
)
8+
9+
// SubjectTokenProviderError is raised when failing to get the subject token from the cloud environment.
10+
type SubjectTokenProviderError struct {
11+
Provider string
12+
Message string
13+
Cause error
14+
}
15+
16+
func (e *SubjectTokenProviderError) Error() string {
17+
if e.Cause != nil {
18+
return fmt.Sprintf("%s provider error: %s: %v", e.Provider, e.Message, e.Cause)
19+
}
20+
return fmt.Sprintf("%s provider error: %s", e.Provider, e.Message)
21+
}
22+
23+
func (e *SubjectTokenProviderError) Unwrap() error {
24+
return e.Cause
25+
}
26+
27+
// OAuthError is raised when there is an error in the RFC-defined OAuth flow, such as failing to exchange the token.
28+
// See https://datatracker.ietf.org/doc/html/rfc8693 for the OAuth 2.0 Token Exchange specification.
29+
type OAuthError struct {
30+
StatusCode int
31+
ErrorCode shared.OAuthErrorCode
32+
ErrorDescription string
33+
}
34+
35+
func (e *OAuthError) Error() string {
36+
return fmt.Sprintf("OAuth error (status %d): %s - %s", e.StatusCode, e.ErrorCode, e.ErrorDescription)
37+
}

auth/middleware.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package auth
2+
3+
import (
4+
"net/http"
5+
)
6+
7+
func WorkloadIdentityMiddleware(
8+
wia *WorkloadIdentityAuth,
9+
httpClient HTTPDoer,
10+
req *http.Request,
11+
next func(*http.Request) (*http.Response, error),
12+
) (*http.Response, error) {
13+
token, err := wia.GetToken(req.Context(), httpClient)
14+
if err != nil {
15+
return nil, err
16+
}
17+
18+
req.Header.Set("Authorization", "Bearer "+token)
19+
20+
resp, err := next(req)
21+
if err != nil || resp == nil || resp.StatusCode != http.StatusUnauthorized {
22+
return resp, err
23+
}
24+
25+
wia.invalidateToken()
26+
27+
if req.Body != nil && req.GetBody == nil {
28+
return resp, nil
29+
}
30+
31+
retryReq := req.Clone(req.Context())
32+
33+
token, err = wia.GetToken(req.Context(), httpClient)
34+
if err != nil {
35+
resp.Body.Close()
36+
return nil, err
37+
}
38+
retryReq.Header.Set("Authorization", "Bearer "+token)
39+
40+
if req.GetBody != nil {
41+
retryReq.Body, err = req.GetBody()
42+
if err != nil {
43+
resp.Body.Close()
44+
return nil, err
45+
}
46+
}
47+
48+
resp.Body.Close()
49+
return next(retryReq)
50+
}

0 commit comments

Comments
 (0)