MCPcopy Index your code
hub / github.com/github/github-mcp-server / beginPKCE

Method beginPKCE

internal/oauth/flow.go:73–154  ·  view source on GitHub ↗

beginPKCE prepares the authorization-code + PKCE flow. It binds the callback server and selects the most secure available display channel: browser auto-open, then URL elicitation, then a tool-response message. On a headless host with a random callback port it diverts to device flow, whose redirect d

(prompter Prompter)

Source from the content-addressed store, hash-verified

71// host with a random callback port it diverts to device flow, whose redirect
72// does not depend on reaching this machine's localhost.
73func (m *Manager) beginPKCE(prompter Prompter) (*flowPlan, error) {
74 state, err := randomState()
75 if err != nil {
76 return nil, err
77 }
78 verifier := oauth2.GenerateVerifier()
79
80 // Bind to all interfaces only inside a container, where the published port
81 // is delivered via eth0 rather than loopback. Native runs stay on loopback.
82 listener, err := listenCallback(m.config.CallbackPort, m.inDocker())
83 if err != nil {
84 return nil, fmt.Errorf("%w: %w", errCallbackBind, err)
85 }
86 if m.inDocker() {
87 // Inside a container the callback binds all interfaces so the published
88 // port is reachable, which also exposes it to the container network.
89 // Publishing to loopback only (e.g. -p 127.0.0.1:%d:%d) keeps the
90 // authorization code off the network.
91 m.logger.Warn(fmt.Sprintf("OAuth callback is listening on all container interfaces; publish it to loopback only (e.g. -p 127.0.0.1:%d:%d) so the authorization code is not exposed on your network", m.config.CallbackPort, m.config.CallbackPort))
92 }
93 cs := newCallbackServer(listener, state)
94
95 oc := m.oauth2Config(cs.redirect)
96 authURL := oc.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier))
97
98 run := func(ctx context.Context) (*oauth2.Token, error) {
99 code, err := cs.wait(ctx)
100 if err != nil {
101 return nil, err
102 }
103 tok, err := oc.Exchange(ctx, code, oauth2.VerifierOption(verifier))
104 if err != nil {
105 return nil, fmt.Errorf("exchanging authorization code: %w", err)
106 }
107 return tok, nil
108 }
109
110 browserErr := m.openURL(authURL)
111 switch {
112 case browserErr == nil:
113 m.logger.Info("opened browser for GitHub authorization")
114 return &flowPlan{run: run}, nil
115 case errors.Is(browserErr, errNoDisplay) && m.config.CallbackPort == 0:
116 // Headless host with a random callback port: every PKCE channel ends in a
117 // redirect to this machine's localhost, which a browser on another machine
118 // (e.g. a remote SSH client) cannot reach — so even URL elicitation would
119 // dead-end. Device flow is the only channel reachable from elsewhere, so
120 // prefer it when the app supports it; otherwise fall through to the manual
121 // authorization URL below for a same-machine browser.
122 plan, deviceErr := m.beginDevice(prompter)
123 if deviceErr == nil {
124 cs.close()
125 m.logger.Info("no display server; using device flow")
126 return plan, nil
127 }
128 m.logger.Debug("device flow unavailable on headless host; offering manual authorization URL", "reason", deviceErr)
129 default:
130 m.logger.Debug("browser auto-open unavailable", "reason", browserErr)

Callers 1

beginMethod · 0.95

Calls 10

oauth2ConfigMethod · 0.95
beginDeviceMethod · 0.95
randomStateFunction · 0.85
listenCallbackFunction · 0.85
newCallbackServerFunction · 0.85
canPromptURLFunction · 0.85
waitMethod · 0.80
closeMethod · 0.80
PromptURLMethod · 0.65
IsMethod · 0.45

Tested by

no test coverage detected