如何使用CLI的OAuth授权代码

qoefvg9y  于 2022-12-03  发布在  其他
关注(0)|答案(1)|浏览(145)

尝试允许我正在开发的CLI通过Web浏览器“登录”并获得用户帐户的访问令牌,类似于gcloud和github的CLI的做法。我意识到它将使用OAuth授权代码流。
但是client_secret呢?我发现github cli根本不在乎暴露它,它就在源代码中:https://github.com/cli/cli/blob/6a8deb1f5a9f2aa0ace2eb154523f3b9f23a05ae/internal/authflow/flow.go#L25-L26
为什么这不是一个问题?或者是吗?
我尚未使用OAuth登录客户端

hsgswve4

hsgswve41#

标准

CLI应用程序是原生公用客户端,应该使用受权程式码流程+ PKCE,而不是固定的客户端密码。它也应该遵循RFC8252的流程,并使用本机HTTP(回送)URI接收浏览器回应。

本实施

看起来github代码here使用了一个客户端密码,而没有使用PKCE。如果使用这个库,你可能需要提供一个客户端密码,但是它不能对用户保密。任何用户都可以很容易地查看它,例如使用HTTP代理工具。

代码

如果基础结构允许您遵循这些标准,那么请尝试类似于此Node.js代码的内容。

* The OAuth flow for a console app
 */
export async function login(): Promise<string> {

    // Set up the authorization request
    const codeVerifier = generateRandomString();
    const codeChallenge = generateHash(codeVerifier);
    const state = generateRandomString();
    const authorizationUrl = buildAuthorizationUrl(state, codeChallenge);

    return new Promise<string>((resolve, reject) => {

        let server: Http.Server | null = null;
        const callback = async (request: Http.IncomingMessage, response: Http.ServerResponse) => {

            if (server != null) {
                
                // Complete the incoming HTTP request when a login response is received
                response.write('Login completed for the console client ...');
                response.end();
                server.close();
                server = null;

                try {

                    // Swap the code for tokens
                    const accessToken = await redeemCodeForAccessToken(request.url!, state, codeVerifier);
                    resolve(accessToken);

                } catch (e: any) {
                    reject(e);
                }
            }
        }

        // Start an HTTP server and listen for the authorization response on a loopback URL, according to RFC8252
        server = Http.createServer(callback);
        server.listen(loopbackPort);
        
        // Open the system browser to begin authentication
        Opener(authorizationUrl);
    });
}

/*
 * Build a code flow URL for a native console app
 */
function buildAuthorizationUrl(state: string, codeChallenge: string): string {

    let url = authorizationEndpoint;
    url += `?client_id=${encodeURIComponent(clientId)}`;
    url += `&redirect_uri=${encodeURIComponent(redirectUri)}`;
    url += '&response_type=code';
    url += `&scope=${scope}`;
    url += `&state=${encodeURIComponent(state)}`;
    url += `&code_challenge=${encodeURIComponent(codeChallenge)}`;
    url += '&code_challenge_method=S256';
    return url;
}

/*
 * Swap the code for tokens using PKCE and return the access token
 */
async function redeemCodeForAccessToken(responseUrl: string, requestState: string, codeVerifier: string): Promise<string> {

    const [code, responseState] = getLoginResult(responseUrl);
    if (responseState !== requestState) {
        throw new Error('An invalid authorization response state was received');
    }

    let body = 'grant_type=authorization_code';
    body += `&client_id=${encodeURIComponent(clientId)}`;
    body += `&redirect_uri=${encodeURIComponent(redirectUri)}`;
    body += `&code=${encodeURIComponent(code)}`;
    body += `&code_verifier=${encodeURIComponent(codeVerifier)}`;

    const options = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body,
    };

    const response = await fetch(tokenEndpoint, options);
    if (response.status >= 400) {
        const details = await response.text();
        throw new Error(`Problem encountered redeeming the code for tokens: ${response.status}, ${details}`);
    }

    const data = await response.json();
    return data.access_token;
}

相关问题