Yeti Verse Connector Hub: Three Ways It Handles a LinkedIn Login
yeti-verse-connector-hub is a public Vite and React repository of mine under github.com/yethikrishna, a connections dashboard for many platforms. I read netlify/functions/linkedin-oauth.ts, src/handlers/linkedin/oauthHandler.ts, src/handlers/linkedin/index.ts, src/handlers/linkedinHandler.ts, the callback page and the model settings panel. I did not run the app or use a LinkedIn app.
What it does well
The Netlify function accepts POST only, reads the client id, secret and redirect URI from environment variables, exchanges the code with LinkedIn, and returns only the access token. The secret never reaches the browser on that path. That is the shape I want.
Where it goes wrong
The repo has a second path next to it. In src/handlers/linkedin/index.ts the connect step takes a client id and client secret from the form, stores both through secureStorage, and builds the authorization URL in the browser. The matching oauthHandler.ts then posts the secret to LinkedIn from client code. A secret typed into a page and kept in the browser is not secret from anyone who can read that browser. The code comments admit the rest: after opening the popup it returns true with a note that it will simulate success.
Two smaller things. The state value comes from Math.random, and the callback page reads only the code parameter, so state is never compared on return. The server function's token then lands in localStorage under linkedinAccessToken. Model keys for Claude, Perplexity and Mistral are kept in localStorage the same way in the settings panel.
What I would change
My opinion: delete the client-side secret path and keep the Netlify function as the only exchange. Generate state with crypto.getRandomValues, store it before the redirect, and reject the callback if it does not match. For the token, a short-lived server session cookie is better than localStorage. I have not confirmed which path the live connect button uses end to end, and I would check that first.