본문 바로가기

vscode-with-tistory

vscode extension 개발일기2: 외부서버 없이 OAuth인증구현

개요

extension을 개발하는 중에 oauth를 이용하여 access token을 받고 이를 통해 api를 받아야 하는 동작을 수행하여야 한다.

그러나 내부에서 웹뷰를 사용하는 것은 한계가 있어서 사용자 브라우저에서 해당 과정을 수행해야 하는데 이 과정에서 access_token을 발급하는 과정을 vscode만으론 관여하기 힘들다는 문제가 있다.

로컬 서버

이 문제를 해결하기 위해 redirect_uri를 위한 웹 서버를 구현해서 실행시켜야 하였다. NodeJS에서 유명한 웹서버 프레임워크와 oauth 라이브러리인 express와 passport 를 사용할려 했다. 그러나 이를 사용할려고 하니 기능에 비해 모듈이 너무 많이 붙어있다는 생각을 하여서 http 소켓프로그래밍으로 작성하기로 하였다.

서버 운용은 2가지 방식이 존재한다.

  1. 외부에서 서버를 운영.

    • oauth의 동작 과정을 그대로 구현할 수 있다. 그러나 서버 운용비용을 충당할 방법이 없다.
  2. Owner가 Client서버를 구동

    • vscode extension에 웹서버를 적재해서 요청이 들어올 때마다 서버를 실행하고 동작시킨다. Client를 vscode에서 제어가 가능하다. 그러나 oauth정보를 코드에 직접 삽입하거나 사용자마다 티스토리 api승인을 받아서 정보를 입력해야한다.

2개의 방안중 2번을 사용하기로 하였다. 2번에서 oauth정보를 코드에 직접 삽입하는 것보다 사용자마다 티스토리 api승인을 받는 방식으로 작성하기로 하였다.

http를 사용하는 이유

oauth2의 특징은 암호화 부분을 https프로토콜에 맞기는 건데 사용자 로컬 컴퓨터에 https를 사용할려면 인증서를 적용시켜야 한다. 그러나 설치된 모든 extension에서 같은 인증서를 가지고 동작된다면 인증서의 의미가 없다고 생각한다. 그리고 개개인이 인증서를 직접 발급하고 이식하는 과정은 티스토리 api승인을 발급하는 과정에 비해 매우 복잡하다. 그래서 https를 사용하지 않고 http를 사용한다.

차트

동작 과정을 그림으로 그리면 다음과 같다.

auth_logic.png

  1. 외부 브라우저를 통해 인증 페이지를 열고 동시에 VSCode 내에서 Client역할을 할 임시서버를 실행한다.
  2. Web에서 인증을 수행하고 Code를 임시 서버로 받는다.
  3. 임시서버가 Code를 통해 Access Token을 요청받는다.
  4. Access Token을 받는다.
  5. Access Token을 받고 Resource Owner에 저장한다.

위의 과정에서 Client를 종료하지 않은데 이유는 추후에 토큰을 갱신할 때 서버를 재가동 시키는 것보다 가동시킨 채로 냅두는 것이 성능 면에서 더 좋을 것이라고 생각했기 때문이다.

Client가 종료하는 조건은 Extension이 disable되었을 때 종료한다.

코드

간단한 http서버를 생성하는 코드이다. 내용은 redirect_url의 파라미터를 파싱하는데 이때 code가 존재하지 않으면 에러가 발생했다고 판단하고 error을 반환해준다. code 따로 error따로 파싱하는 것보다 최초로 파싱할 때 에러부분도 파싱을 시도하여 파싱하는 부분이 분리되지 않도록 하였다.

//api.ts
import "dotenv/config";
import * as vscode from "vscode";
import axios from "axios";
import * as dotenv from "dotenv";

dotenv.config({path: 'D:\\MyProject\\vscode-with-tistory\\.env'});

const {CLIENT_ID,REDIRECT_URI,SECRET_KEY}=process.env;
let ACCESS_TOKEN: String='';
const API_LIST={
    AUTHORIZATION:"https://www.tistory.com/oauth/authorize",
    OATH_ACCESSTOKEN:"https://www.tistory.com/oauth/access_token"
}

export const authorizateTistory = async (context: vscode.ExtensionContext) => {
    vscode.env.openExternal(
        vscode.Uri.parse(
            `${API_LIST.AUTHORIZATION}=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code`
            )
        );
};

export const getAccessToken=async (code: string)=>{
    const {data:{
        access_token
    }} =await axios.get(API_LIST.OATH_ACCESSTOKEN,{
        data:{
            client_id:CLIENT_ID,
            client_secret:SECRET_KEY,
            redirect_uri:REDIRECT_URI,
            code,
            grant_type:'authorization_code'
        }
    });
    ACCESS_TOKEN=access_token;
}

api호출부분이며 각각의 api는 다음과 같다.

  • authorizateTistory : 브라우저로 티스토리 인증을 시도하기 위해 브라우저로 인증 창을 열어준다. 해당 코드를 extensions에 작성하지 않은 이유는 이 과정에서 client_id, redirect_uri의 정보가 필요한데 해당 정보를 extensions.ts에서 작성하는 것이 코드를 깨트리는 것이라고 생각했다.
  • getAccessToken: authorizationTistory를 통해 받은 code를 이용하여 AccessToken을 발급하는 과정이다. AccessToken은 변수에 저장하였으며 추후에 이를 로컬에 저장할 방법을 찾을 것이다.
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import {authorizateTistory} from './apis';
import { client } from './Client';
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
	// Use the console to output diagnostic information (console.log) and errors (console.error)
	// This line of code will only be executed once when your extension is activated
	console.log('Congratulations, your extension "vscode-with-tistory" is now active!');

	// The command has been defined in the package.json file
	// Now provide the implementation of the command with registerCommand
	// The commandId parameter must match the command field in package.json
	let disposable = vscode.commands.registerCommand('vscode-with-tistory.login', () => {
		if(!client.listening){
			client.listen(5500,()=>{
				console.log('server is running');
			});
		}
		authorizateTistory(context);
	});

	context.subscriptions.push(disposable);
}

export function deactivate() {client.close(()=>console.log("Stop Client"))}

client서버가 동작하고 있지 않으면 서버를 실행시키고 authorizationTistory를 수행한다.

deactivate을 수행할 때 서버를 닫도록 한다. ⇒ extension을 비활성화 하여도 서버가 동작하는 상황을 방지한다.

get Access Token · dev-green-flamingo/vscode-with-tistory@ae3663d