DEV Community

Cover image for I Built a 1-Click OAuth Flow for VS Code to Stop Copying API Keys
freerave
freerave

Posted on

I Built a 1-Click OAuth Flow for VS Code to Stop Copying API Keys

Hey DEV Community!

If you've ever built a CLI tool or a desktop extension that interacts with social media platforms, you know the absolute nightmare of user authentication. The standard workflow usually involves begging the user to go to a developer portal, generate an API key, copy it, and paste it into a .env or settings.json file.

I got completely tired of doing that. So, for the massive v2.4.0 update of my open-source VS Code extension, DotShare, I decided to engineer a seamless, browser-to-editor OAuth 2.0 flow.

Here is a deep dive into how I built it, the code behind it, and how you can do it too.

The Problem: Browsers vs. Desktop Apps

When building a web app, OAuth is easy: you redirect the user to LinkedIn/X, they authenticate, and the platform redirects them back to your https:// callback URL.

But VS Code is a desktop application. You can't redirect a browser tab to localhost reliably without running a clunky local server on the user's machine.

The Solution: VS Code URI Handlers

VS Code has a built-in API called vscode.window.registerUriHandler. It allows the editor to listen for custom deep links (e.g., vscode://publisher.extension-name/auth).

Here is how I implemented the "Magic Redirect" in TypeScript:

typescript
import * as vscode from 'vscode';

export class AuthUriHandler implements vscode.UriHandler {
    // This triggers when the browser redirects back to vscode://...
    public handleUri(uri: vscode.Uri): void {
        const queryParams = new URLSearchParams(uri.query);
        const token = queryParams.get('token');
        const platform = queryParams.get('platform');

        if (token && platform) {
            // Securely store the token using VS Code's native SecretStorage
            this.storeEncryptedToken(platform, token);

            vscode.window.showInformationMessage(
                `Status: 200 OK ✅ Successfully connected to ${platform}!`
            );

            // Notify the Webview UI to turn the connect button green 🟢
            this.updateWebviewUI(platform, 'connected');
        }
    }

    private async storeEncryptedToken(platform: string, token: string) {
        const secrets = vscode.context.secrets;
        await secrets.store(`${platform}_oauth_token`, token);
    }
}

// In extension.ts activation:
vscode.window.registerUriHandler(new AuthUriHandler());
Enter fullscreen mode Exit fullscreen mode

Now, my Vercel auth server simply redirects to vscode://kareem2099.dotshare/auth?platform=linkedin&token=XYZ, and VS Code instantly wakes up and intercepts the token securely!

Building a "Smart" Hashtag Engine
Another feature I shipped in v2.4.0 is the Smart Hashtag System.

When using AI (like Claude or Gemini) to generate posts, they often spit out generic, cringy hashtags. I wanted DotShare to generate tags based on actual workspace data.

Instead of guessing the project name from random NPM keywords (which sometimes resulted in #lint or #test), I wrote a context builder that extracts the exact workspace folder name and sanitizes it:

// Inside HashtagService.ts
export function getProjectHashtags(context: HashtagContext): string[] {
    const tags: string[] = [];

    // 1. Get the actual project name from the VS Code workspace
    if (context.projectName) {
        // Sanitize: Remove spaces and special chars for a clean hashtag
        const cleanName = context.projectName.replace(/[^a-zA-Z0-9]/g, '');

        if (cleanName.length > 2) {
            // Capitalize the first letter (e.g., dotshare -> #Dotshare)
            const capitalizedName = cleanName.charAt(0).toUpperCase() + cleanName.slice(1);
            tags.push(`#${capitalizedName}`);
        }
    }

    // 2. Add supplementary tags from package.json/Cargo.toml
    if (context.keywords && context.keywords.length > 0) {
        const validKeywords = context.keywords
            .filter(k => k.length > 2 && k.length < 15)
            .slice(0, 2); // Keep it clean, max 2 keywords

        for (const keyword of validKeywords) {
            const cleanKeyword = keyword.replace(/[^a-zA-Z0-9]/g, '');
            tags.push(`#${cleanKeyword.charAt(0).toUpperCase() + cleanKeyword.slice(1)}`);
        }
    }

    return tags;
}
Enter fullscreen mode Exit fullscreen mode

Note: We completely disable this engine when posting to Reddit, because Reddit communities hate hashtags! (It's the small UX details that matter).

🎬 See it in Action (40 Seconds)
You can see the 1-Click OAuth flow and the AI post generation working seamlessly together here:

Try it out!
If you are a developer who builds in public, shares snippets, or just wants to log your journey without leaving your editor, I built this for you.

We just crossed 470+ active users, and I would love for you to try it out and critique the code/architecture!

Download DotShare from the VS Code Marketplace

Let me know what you think of the URI Handler approach in the comments! 👇

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.