Skip to content

Commit cd6dc19

Browse files
madhav-dbclaude
andauthored
Token Federation for Go Driver (3/3) (#292)
Two comprehensive examples demonstrating token provider usage: 1. token_federation: Simple external token provider with federation 2. browser_oauth_federation: Full browser OAuth flow with automatic token exchange Both examples show real-world integration patterns for custom authentication. --------- Co-authored-by: Claude <[email protected]>
1 parent 73f2a36 commit cd6dc19

File tree

2 files changed

+259
-0
lines changed

2 files changed

+259
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Example: Browser OAuth (U2M) Authentication
2+
//
3+
// This example demonstrates User-to-Machine (U2M) OAuth authentication,
4+
// which opens a browser for the user to log in interactively.
5+
//
6+
// Environment variables:
7+
// - DATABRICKS_HOST: Databricks workspace hostname
8+
// - DATABRICKS_HTTPPATH: SQL warehouse HTTP path
9+
package main
10+
11+
import (
12+
"context"
13+
"database/sql"
14+
"fmt"
15+
"log"
16+
"os"
17+
"time"
18+
19+
dbsql "github.com/databricks/databricks-sql-go"
20+
"github.com/databricks/databricks-sql-go/auth/oauth/u2m"
21+
"github.com/joho/godotenv"
22+
)
23+
24+
func main() {
25+
// Load .env file if present
26+
if err := godotenv.Load(); err != nil {
27+
log.Printf("Note: .env file not found")
28+
}
29+
30+
host := os.Getenv("DATABRICKS_HOST")
31+
httpPath := os.Getenv("DATABRICKS_HTTPPATH")
32+
33+
if host == "" || httpPath == "" {
34+
log.Fatal("Required: DATABRICKS_HOST and DATABRICKS_HTTPPATH")
35+
}
36+
37+
fmt.Println("Browser OAuth (U2M) Example")
38+
fmt.Println("===========================")
39+
fmt.Printf("Host: %s\n", host)
40+
fmt.Printf("Path: %s\n\n", httpPath)
41+
42+
// Create U2M authenticator - this will open a browser for login
43+
authenticator, err := u2m.NewAuthenticator(host, 2*time.Minute)
44+
if err != nil {
45+
log.Fatalf("Failed to create authenticator: %v", err)
46+
}
47+
48+
// Create database connector
49+
connector, err := dbsql.NewConnector(
50+
dbsql.WithServerHostname(host),
51+
dbsql.WithHTTPPath(httpPath),
52+
dbsql.WithAuthenticator(authenticator),
53+
)
54+
if err != nil {
55+
log.Fatalf("Failed to create connector: %v", err)
56+
}
57+
58+
db := sql.OpenDB(connector)
59+
defer db.Close()
60+
61+
// Test connection - this triggers browser OAuth flow
62+
fmt.Println("Connecting (browser will open for login)...")
63+
if err := db.Ping(); err != nil {
64+
log.Fatalf("Connection failed: %v", err)
65+
}
66+
fmt.Println("✓ Connected successfully")
67+
68+
// Run test queries
69+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
70+
defer cancel()
71+
72+
var result int
73+
if err := db.QueryRowContext(ctx, "SELECT 1").Scan(&result); err != nil {
74+
log.Fatalf("Query failed: %v", err)
75+
}
76+
fmt.Printf("✓ SELECT 1 = %d\n", result)
77+
78+
var user string
79+
if err := db.QueryRowContext(ctx, "SELECT CURRENT_USER()").Scan(&user); err != nil {
80+
log.Fatalf("Query failed: %v", err)
81+
}
82+
fmt.Printf("✓ Logged in as: %s\n", user)
83+
}

examples/token_federation/main.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Example: Token Provider Authentication
2+
//
3+
// This example demonstrates different ways to provide authentication tokens:
4+
// 1. Static token - hardcoded/env token
5+
// 2. External token - dynamic token from a function
6+
// 3. Custom provider - full TokenProvider implementation
7+
//
8+
// Environment variables:
9+
// - DATABRICKS_HOST: Databricks workspace hostname
10+
// - DATABRICKS_HTTPPATH: SQL warehouse HTTP path
11+
// - DATABRICKS_ACCESS_TOKEN: Access token for authentication
12+
// - TOKEN_EXAMPLE: Which example to run (static, external, custom)
13+
package main
14+
15+
import (
16+
"context"
17+
"database/sql"
18+
"database/sql/driver"
19+
"fmt"
20+
"log"
21+
"os"
22+
"time"
23+
24+
dbsql "github.com/databricks/databricks-sql-go"
25+
"github.com/databricks/databricks-sql-go/auth/tokenprovider"
26+
"github.com/joho/godotenv"
27+
)
28+
29+
func main() {
30+
if err := godotenv.Load(); err != nil {
31+
log.Printf("Note: .env file not found")
32+
}
33+
34+
example := os.Getenv("TOKEN_EXAMPLE")
35+
if example == "" {
36+
example = "static"
37+
}
38+
39+
switch example {
40+
case "static":
41+
runStaticExample()
42+
case "external":
43+
runExternalExample()
44+
case "custom":
45+
runCustomProviderExample()
46+
default:
47+
log.Fatalf("Unknown example: %s (use: static, external, custom)", example)
48+
}
49+
}
50+
51+
// runStaticExample uses a static token from environment variable
52+
func runStaticExample() {
53+
fmt.Println("Static Token Example")
54+
fmt.Println("====================")
55+
56+
host := os.Getenv("DATABRICKS_HOST")
57+
httpPath := os.Getenv("DATABRICKS_HTTPPATH")
58+
token := os.Getenv("DATABRICKS_ACCESS_TOKEN")
59+
60+
if host == "" || httpPath == "" || token == "" {
61+
log.Fatal("Required: DATABRICKS_HOST, DATABRICKS_HTTPPATH, DATABRICKS_ACCESS_TOKEN")
62+
}
63+
64+
connector, err := dbsql.NewConnector(
65+
dbsql.WithServerHostname(host),
66+
dbsql.WithHTTPPath(httpPath),
67+
dbsql.WithStaticToken(token),
68+
)
69+
if err != nil {
70+
log.Fatal(err)
71+
}
72+
73+
testConnection(connector)
74+
}
75+
76+
// runExternalExample uses a function that returns tokens on-demand
77+
func runExternalExample() {
78+
fmt.Println("External Token Example")
79+
fmt.Println("======================")
80+
81+
host := os.Getenv("DATABRICKS_HOST")
82+
httpPath := os.Getenv("DATABRICKS_HTTPPATH")
83+
84+
if host == "" || httpPath == "" {
85+
log.Fatal("Required: DATABRICKS_HOST, DATABRICKS_HTTPPATH")
86+
}
87+
88+
// Token function - called each time a token is needed
89+
// In practice, this could read from a file, call an API, etc.
90+
tokenFunc := func() (string, error) {
91+
token := os.Getenv("DATABRICKS_ACCESS_TOKEN")
92+
if token == "" {
93+
return "", fmt.Errorf("DATABRICKS_ACCESS_TOKEN not set")
94+
}
95+
fmt.Println(" → Token fetched from external source")
96+
return token, nil
97+
}
98+
99+
connector, err := dbsql.NewConnector(
100+
dbsql.WithServerHostname(host),
101+
dbsql.WithHTTPPath(httpPath),
102+
dbsql.WithExternalToken(tokenFunc),
103+
)
104+
if err != nil {
105+
log.Fatal(err)
106+
}
107+
108+
testConnection(connector)
109+
}
110+
111+
// runCustomProviderExample uses a custom TokenProvider implementation
112+
func runCustomProviderExample() {
113+
fmt.Println("Custom Provider Example")
114+
fmt.Println("=======================")
115+
116+
host := os.Getenv("DATABRICKS_HOST")
117+
httpPath := os.Getenv("DATABRICKS_HTTPPATH")
118+
token := os.Getenv("DATABRICKS_ACCESS_TOKEN")
119+
120+
if host == "" || httpPath == "" || token == "" {
121+
log.Fatal("Required: DATABRICKS_HOST, DATABRICKS_HTTPPATH, DATABRICKS_ACCESS_TOKEN")
122+
}
123+
124+
// Custom provider with expiry tracking
125+
provider := &ExpiringTokenProvider{
126+
token: token,
127+
lifetime: 1 * time.Hour,
128+
}
129+
130+
connector, err := dbsql.NewConnector(
131+
dbsql.WithServerHostname(host),
132+
dbsql.WithHTTPPath(httpPath),
133+
dbsql.WithTokenProvider(provider),
134+
)
135+
if err != nil {
136+
log.Fatal(err)
137+
}
138+
139+
testConnection(connector)
140+
fmt.Printf(" Token expires: %s\n", provider.expiresAt.Format(time.RFC3339))
141+
}
142+
143+
// testConnection verifies the connection works
144+
func testConnection(connector driver.Connector) {
145+
db := sql.OpenDB(connector)
146+
defer db.Close()
147+
148+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
149+
defer cancel()
150+
151+
var result int
152+
if err := db.QueryRowContext(ctx, "SELECT 1").Scan(&result); err != nil {
153+
log.Fatalf("Query failed: %v", err)
154+
}
155+
fmt.Printf("✓ Connected, SELECT 1 = %d\n", result)
156+
}
157+
158+
// ExpiringTokenProvider is a custom TokenProvider with expiry support
159+
type ExpiringTokenProvider struct {
160+
token string
161+
lifetime time.Duration
162+
expiresAt time.Time
163+
}
164+
165+
func (p *ExpiringTokenProvider) GetToken(ctx context.Context) (*tokenprovider.Token, error) {
166+
p.expiresAt = time.Now().Add(p.lifetime)
167+
return &tokenprovider.Token{
168+
AccessToken: p.token,
169+
TokenType: "Bearer",
170+
ExpiresAt: p.expiresAt,
171+
}, nil
172+
}
173+
174+
func (p *ExpiringTokenProvider) Name() string {
175+
return "expiring-token"
176+
}

0 commit comments

Comments
 (0)