DEV Community

Avinash Pokhrel
Avinash Pokhrel

Posted on

Building a Google OAuth CLI in Rust with PKCE (and surviving the borrow checker)

Building a Google OAuth CLI in Rust with PKCE

TL;DR: I built a tiny CLI that opens a Google login in your browser, receives the OAuth callback, exchanges the code using PKCE, and prints basic public profile info (email, name, picture). It took me about 5 hours in Rust, mainly because of ownership, String vs &str, and lifetime wrangling—but the result is a clean, secure local flow that avoids shipping secrets in source control.


Repository: Source Code


Why I did this

I wondered how Github CLI login works under the hood, and I wanted to build a similar flow for Google. So I decided to implement it myself.

Also: I wanted to do it in Rust.


The minimal shape of the flow

High level, the app does these things:

  1. Load client config from environment variables
  2. Generate a PKCE code_verifier and code_challenge
  3. Start a local HTTP server bound to localhost:0 (OS chooses the port)
  4. Build and print the Google authorization URL
  5. You open the URL and log in
  6. Google redirects to http://localhost:<port>/oauth/google/callback?code=...
  7. The app exchanges the code for tokens using the original code_verifier
  8. The app decodes the id_token payload and prints basic public profile info:
    • email
    • name
    • picture

That's it. No browser automation, no pasting codes - just a local browser flow.


Why PKCE?

If you build a non-confidential client (CLI, desktop, mobile), you can't assume a client secret will stay secret. PKCE ensures that even if an authorization code is intercepted, the attacker can't redeem it without the original code_verifier. The server verifies that the code_challenge sent during login matches the code_verifier sent during the token exchange.


The Rust experience (short)

  • It took longer than Python/JS would have. Much longer.
  • The protocol itself is simple; the trouble in my case was dealing with lifetimes, moving/borrowing strings into collections, and convincing the compiler that nothing will outlive its owner.
  • The result is robust and fast, but plan for extra time if you're learning Rust and OAuth at the same time.

If you're in a hurry and don't care about learning Rust, use Python or JS for a prototype.


Important setup note

Before running this, create an OAuth client in Google Cloud Console and choose "Desktop app" as the client type. This makes the client configuration suitable for local callback flows. After that, copy /.env.example to /.env and fill only these two values:

  • GOOGLE_OAUTH_CLIENT_ID
  • GOOGLE_OAUTH_CLIENT_SECRET

Everything else (scope, authorization/token endpoints) can be copied from .env.example shipped in the repo.


Running it

  1. Create the desktop OAuth client in Google Cloud.
  2. Copy .env.example to .env and set GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET.
  3. cargo run --release (or build and run the binary).
  4. The CLI prints an authorization URL—open it in your browser and log in.
  5. After the Google consent screen, the browser will redirect to the temporary localhost server; the CLI prints your email, name, and picture.

Note: the server binds to localhost:0, so the OS chooses a free port. This avoids port collisions.


Security caveats (this is a learning project)

  • I use PKCE, which is appropriate for public clients.
  • The ID token payload is decoded and read; I do not (in this learning version) fully validate the JWT signature, issuer, audience, nonce, or expiration. For production use, you must validate these claims and verify the token signature.
  • The app reads client secrets from .env.

Closing thoughts

This project was my way to learn OAuth (the protocol) and Rust (the language) together. The final flow is clean: the CLI asks the user to log in and returns the public profile info without ever storing secrets in the repo.

If you want to try it:

  • Fork or clone the repo
  • Create a Desktop OAuth client in Google Cloud
  • Set the two env vars and run it

Top comments (0)