Skip to content

OpenAI Login with Subscription Support #2035

Closed as duplicate
Closed as duplicate

Description

@ameent

Codex CLI recently implemented this and it would be great if the same capability exists in opencode as well. As a ChatGPT subscriber, I don't want to burn money buying API credits and rather use my subscription, I'm already paying for.

It looks like OpenAI has a typical OAuth setup, with refresh tokens et. al implemented. You can see the core refresh flow at https://github.com/openai/codex/blob/main/codex-rs/login/src/lib.rs#L310

You can see the initial login request at https://github.com/openai/codex/blob/main/codex-rs/login/src/server.rs#L318 which also creates a local server and the 'redirect_url' of the authorization request is set to the local server which receives the token from the authorization server.

We likely need to register an OAuth client application for opencode and obtain a client_id that can be used for the authentication flow. What would be perhaps, even a better feature, is to have generic support for OAuth-supporting backends/authorization servers so that one could configure the login flow in the 'providers' configuration file directly. For that, one would want to have a configurable authorization server URL (e.g. https://accounts.google.com/o/oauth2/v2/auth), a set of scopes (e.g. "openid", "profile", "email"), and a token URL to obtain the token and optionally automatically refresh it.

Thanks!

Here's a self contained example using the oauth2 package:

package main

import (
	"context"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"

	"golang.org/x/oauth2"
)

// Configurable settings
var (
	clientID     = os.Getenv("OAUTH_CLIENT_ID") // only client_id required
	redirectURL  = "http://localhost:8080/callback"
	authServerAuthURL  = "https://accounts.google.com/o/oauth2/v2/auth"
	authServerTokenURL = "https://oauth2.googleapis.com/token"
	scopes             = []string{"openid", "profile", "email"}
)

var (
	oauthConfig *oauth2.Config
	codeVerifier string
	tokenStore   *oauth2.Token
)

func main() {
	oauthConfig = &oauth2.Config{
		ClientID:    clientID,
		RedirectURL: redirectURL,
		Scopes:      scopes,
		Endpoint: oauth2.Endpoint{
			AuthURL:  authServerAuthURL,
			TokenURL: authServerTokenURL,
		},
	}

	http.HandleFunc("/", handleMain)
	http.HandleFunc("/login", handleLogin)
	http.HandleFunc("/callback", handleCallback)

	log.Println("Server started at http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func handleMain(w http.ResponseWriter, r *http.Request) {
	if tokenStore == nil {
		fmt.Fprintf(w, `<a href="/login">Login with OAuth (PKCE)</a>`)
		return
	}
	data, _ := json.MarshalIndent(tokenStore, "", "  ")
	fmt.Fprintf(w, "Access token stored:\n<pre>%s</pre>", string(data))
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
	// Generate PKCE verifier & challenge
	codeVerifier = generateCodeVerifier()
	codeChallenge := generateCodeChallenge(codeVerifier)

	// Construct Auth URL with PKCE params
	authURL := oauthConfig.AuthCodeURL("state-token",
		oauth2.AccessTypeOffline,
		oauth2.SetAuthURLParam("code_challenge", codeChallenge),
		oauth2.SetAuthURLParam("code_challenge_method", "S256"),
	)

	http.Redirect(w, r, authURL, http.StatusFound)
}

func handleCallback(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")
	if code == "" {
		http.Error(w, "No code in request", http.StatusBadRequest)
		return
	}

	// Exchange code with PKCE verifier
	token, err := oauthConfig.Exchange(
		context.Background(),
		code,
		oauth2.SetAuthURLParam("code_verifier", codeVerifier),
	)
	if err != nil {
		http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
		return
	}

	tokenStore = token
	data, _ := json.MarshalIndent(token, "", "  ")
	fmt.Fprintf(w, "OAuth2 PKCE token received:\n<pre>%s</pre>", string(data))
}

// --- Helpers for PKCE ---
func generateCodeVerifier() string {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		panic(err)
	}
	return base64.RawURLEncoding.EncodeToString(b)
}

func generateCodeChallenge(verifier string) string {
	h := sha256.New()
	h.Write([]byte(verifier))
	return base64.RawURLEncoding.EncodeToString(h.Sum(nil))
}

Activity

  1. github-actions commented on Aug 18, 2025

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Both of these issues are requesting the same core functionality - using OpenAI subscription authentication instead of API keys, which is exactly what this issue is asking for.

    Feel free to ignore if none of these address your specific case.

  2. rekram1-node commented on Aug 18, 2025

    @rekram1-node
    Contributor

    thanks for bringing this up, closing as duplicate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions