| layout | title | description |
|---|---|---|
default |
Auth API |
OAuth 2.0 authentication for YouTube APIs with automatic token refresh. |
Handle OAuth 2.0 authentication for YouTube APIs:
OAuth Flow: Complete Google OAuth 2.0 implementation
- Generate authorization URLs
- Exchange codes for tokens
- Automatic token refresh
Device Code Flow: For limited-input devices
- TVs, consoles, and CLI applications
- User enters code on separate device
- Automatic polling for authorization
Service Accounts: Server-to-server authentication
- JWT-based authentication
- No user interaction required
- Domain-wide delegation support
Token Management: Secure token handling
- Thread-safe token storage
- Automatic refresh before expiry
- Callbacks for token events
const (
// ScopeLiveChat grants read access to live chat messages.
ScopeLiveChat = "https://www.googleapis.com/auth/youtube"
// ScopeLiveChatModerate grants moderator access to live chat.
ScopeLiveChatModerate = "https://www.googleapis.com/auth/youtube.force-ssl"
// ScopeReadOnly grants read-only access to YouTube account.
ScopeReadOnly = "https://www.googleapis.com/auth/youtube.readonly"
// ScopeUpload grants access to upload videos and manage playlists.
ScopeUpload = "https://www.googleapis.com/auth/youtube.upload"
// ScopePartner grants access to YouTube Analytics.
ScopePartner = "https://www.googleapis.com/auth/youtubepartner"
)Create a new OAuth client with configuration.
authClient := auth.NewAuthClient(auth.Config{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
RedirectURL: "http://localhost:8080/callback",
Scopes: []string{
auth.ScopeLiveChat,
auth.ScopeLiveChatModerate,
},
})authClient := auth.NewAuthClient(config,
auth.WithHTTPClient(customHTTPClient),
auth.WithToken(existingToken),
auth.WithRefreshEarly(5*time.Minute),
auth.WithOnTokenRefresh(func(token *auth.Token) {
// Save token to storage
}),
auth.WithOnRefreshError(func(err error) {
log.Printf("Token refresh failed: %v", err)
}),
)Generate the URL to redirect users for authorization.
state := generateRandomState() // Use a cryptographically secure random string
url := authClient.AuthorizationURL(state)
// Redirect user to url
http.Redirect(w, r, url, http.StatusFound)url := authClient.AuthorizationURL(state,
auth.WithPrompt("consent"), // Force consent screen
auth.WithLoginHint("user@example.com"), // Hint which account
)Exchange an authorization code for a token.
// In your callback handler:
code := r.URL.Query().Get("code")
token, err := authClient.Exchange(ctx, code)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Access token: %s\n", token.AccessToken)
fmt.Printf("Expires: %s\n", token.Expiry)Get the current token (thread-safe).
token := authClient.Token()
if token != nil {
fmt.Printf("Access token: %s\n", token.AccessToken)
fmt.Printf("Valid: %v\n", token.Valid())
}Set a token (e.g., loaded from storage).
authClient.SetToken(&auth.Token{
AccessToken: savedAccessToken,
RefreshToken: savedRefreshToken,
Expiry: savedExpiry,
Scopes: savedScopes,
})Get a valid access token, automatically refreshing if expired.
accessToken, err := authClient.AccessToken(ctx)
if err != nil {
log.Fatal(err)
}
// Use accessToken for API callsManually refresh the access token.
newToken, err := authClient.Refresh(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("New access token: %s\n", newToken.AccessToken)Start automatic token refresh in the background.
err := authClient.StartAutoRefresh(ctx)
if err != nil {
log.Fatal(err)
}
// Token will be refreshed automatically before expiryStop the auto-refresh goroutine.
authClient.StopAutoRefresh()type Token struct {
AccessToken string
TokenType string
RefreshToken string
Expiry time.Time
Scopes []string
}// Check if token is valid (not expired)
valid := token.Valid()
// Get access token (thread-safe)
accessToken := token.GetAccessToken()
// Clone token (for safe passing between goroutines)
clone := token.Clone()
// Serialize to JSON
data, err := token.MarshalJSON()
// Deserialize from JSON
err := token.UnmarshalJSON(data)package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/Its-donkey/yougopher/youtube/auth"
)
func main() {
authClient := auth.NewAuthClient(auth.Config{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
RedirectURL: "http://localhost:8080/callback",
Scopes: []string{auth.ScopeLiveChat, auth.ScopeLiveChatModerate},
},
auth.WithOnTokenRefresh(func(token *auth.Token) {
log.Println("Token refreshed, save to storage...")
}),
)
// Step 1: Redirect to authorization URL
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
state := "random-state-string" // Use crypto/rand in production
url := authClient.AuthorizationURL(state)
http.Redirect(w, r, url, http.StatusFound)
})
// Step 2: Handle callback
http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
token, err := authClient.Exchange(r.Context(), code)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Start auto-refresh
authClient.StartAutoRefresh(context.Background())
fmt.Fprintf(w, "Authenticated! Token expires: %s", token.Expiry)
})
log.Println("Visit http://localhost:8080/login to authenticate")
log.Fatal(http.ListenAndServe(":8080", nil))
}For devices with limited input capabilities (TVs, consoles, CLI applications).
Create a device code flow client.
deviceClient := auth.NewDeviceClient(auth.DeviceConfig{
ClientID: "your-client-id",
Scopes: []string{auth.ScopeLiveChat},
})Initiate the device authorization flow.
authResp, err := deviceClient.RequestDeviceCode(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Go to %s\n", authResp.VerificationURL)
fmt.Printf("Enter code: %s\n", authResp.UserCode)
fmt.Printf("Code expires in %d seconds\n", authResp.ExpiresIn)Poll for authorization (blocking).
token, err := deviceClient.PollForToken(ctx, authResp)
if err != nil {
var deviceErr *auth.DeviceAuthError
if errors.As(err, &deviceErr) {
if deviceErr.IsAccessDenied() {
log.Println("User denied access")
}
if deviceErr.IsExpired() {
log.Println("Code expired, request new code")
}
}
log.Fatal(err)
}
fmt.Printf("Authenticated! Access token: %s\n", token.AccessToken)Poll asynchronously with cancel support.
tokenCh, errCh, cancel := deviceClient.PollForTokenAsync(ctx, authResp)
select {
case token := <-tokenCh:
fmt.Printf("Got token: %s\n", token.AccessToken)
case err := <-errCh:
log.Fatal(err)
case <-time.After(5 * time.Minute):
cancel() // Cancel polling
log.Fatal("Timeout waiting for authorization")
}For server-to-server communication without user interaction.
Load from Google Cloud service account JSON file.
jsonData, err := os.ReadFile("service-account.json")
if err != nil {
log.Fatal(err)
}
client, err := auth.NewServiceAccountClientFromJSON(jsonData,
[]string{auth.ScopeReadOnly, auth.ScopePartner},
)
if err != nil {
log.Fatal(err)
}Create from configuration values.
client, err := auth.NewServiceAccountClient(auth.ServiceAccountConfig{
Email: "service@project.iam.gserviceaccount.com",
PrivateKey: pemEncodedPrivateKey,
Scopes: []string{auth.ScopeReadOnly},
})client, err := auth.NewServiceAccountClient(config,
auth.WithServiceAccountHTTPClient(customHTTPClient),
auth.WithServiceAccountRefreshEarly(10*time.Minute),
auth.WithSubject("user@domain.com"), // Domain-wide delegation
auth.WithServiceAccountOnTokenRefresh(func(token *auth.Token) {
log.Println("Token refreshed")
}),
)Get a valid access token (fetches new token if needed).
accessToken, err := client.AccessToken(ctx)
if err != nil {
log.Fatal(err)
}
// Use accessToken for API callsExplicitly fetch a new token.
token, err := client.FetchToken(ctx)
if err != nil {
log.Fatal(err)
}Service accounts support auto-refresh like OAuth clients.
err := client.StartAutoRefresh(ctx)
if err != nil {
log.Fatal(err)
}
// Token will be refreshed automatically before expiry
defer client.StopAutoRefresh()AuthClient, DeviceClient, and ServiceAccountClient are all safe for concurrent use. All token operations are protected by mutex locks.