diff --git a/blog/2023-08-04-oauth-plugin/index.mdx b/blog/2023-08-04-oauth-plugin/index.mdx new file mode 100644 index 0000000..4e26db0 --- /dev/null +++ b/blog/2023-08-04-oauth-plugin/index.mdx @@ -0,0 +1,44 @@ +--- +slug: notebook-tools-for-llms +title: "OAuth for ChatGPT Plugins" +authors: [kafonek] +description: "How Noteable added OAuth to its ChatGPT Plugin" +draft: true +# image: "./TODO.png" +tags: [chatgpt, plugins, chatgpt plugins, oauth, security, architecture] +--- + +Hello plugin community. I work on the backend engineering team at Noteable, which includes development of the Noteable ChatGPT plugin. Today I wanted to share a bit of a guide to OAuth for plugin developers based on documentation and retrospectives I’ve written for our internal consumption. Feel free to re-use and re-purpose the images and text below for your own projects if it helps. I’m happy to answer questions in the comments or direct messages. + +I’m going to start out at a very high level and then work down into some more complicated use cases using our own system as an example. There are many different OAuth providers out there, and you can always roll your own. We happen to use Auth0, so that’s what my diagrams are geared towards. My words here should complement official OpenAI tutorials and Auth0 docs:: + +- https://platform.openai.com/docs/plugins/authentication/oauth +- https://auth0.com/docs/authenticate/protocols/oauth#authorization-endpoint + +Let’s start with why a plugin would use OAuth, compared to “no auth” or “service level auth”. Simply put, if your plugin or downstream API needs to know about a logged in user, use OAuth. For instance, if you were writing a wikipedia-reading plugin you could skip Oauth because you don’t need to have a logged in user to read Wiki. If the LLM is creating Notebooks and running code via Noteable plugin, which is chock full of RBAC and user-context-aware features, we need to know what user account the request is for. + +As a plugin developer, getting started with OAuth can be a bit tricky. You can’t test with localhost plugin development workflow. If you try to “develop your own plugin” and point to localhost, and your manifest file has anything other than “none” in the auth section, you’ll get a message saying “Only auth type `none` is supported for localhost plugins”. + +One option is to use a tool like ngrok to proxy requests to your localhost, just make sure you adjust the allowed redirect domains in your OAuth provider every time you spin up a new proxy. Luckily for me, Noteable has several integration and staging domains I can test with. + +Next, what does it look like to users of your plugin? When they install your plugin, they’ll be redirected to your OAuth provider to login. When that’s complete, the user can activate your plugin for new chats and the LLM will include a JWT in an Authorization: bearer header for all HTTP requests to your plugin. If you look at your browser network tab, you can see part of the flow but not all of it. + +Your plugin can then do two things with the JWT: validate that it’s signed by the OAuth provider and decode it to get a payload of information about the user. https://jwt.io is a great resource for seeing what a generic JWT looks like and decoding (but not validating) JWTs in testing and debugging. OAuth providers will typically publish their public signing keys at a /.well-known/jwks.json endpoint. + +I’ll pause for a moment to talk about the values you configure in ChatGPT when clicking “develop your own plugin” and what goes in the manifest file. If you’re using auth0, the client_url in the manifest file should be the /authorize endpoint and the authorization_url is the /oauth/token endpoint for your tenant. The redirect_uri is controlled by ChatGPT and will be built using your plugin id, make sure to configure your OAuth provider to allow that redirect path. + +If you’ve worked with OAuth in typical frontend/backend applications before, then it can help to think about ChatGPT and the plugin as effectively just another frontend to your API. The Noteable plugin as an example is mostly a passthrough to a subset of the overall Noteable API, with some shortened endpoints and an OpenAPI schema tailored for the LLM. + +One nice perk of auth0 is that you can configure different “Applications” (the auth0 term, not the generic sense of the word) in the same tenant. Those can display different landing pages for users that are coming into your login/signup flow from a normal frontend vs ChatGPT. As well, you may want to customize which logins are allowed. As a practical example, we had to disable username/password signup from ChatGPT since we enforce email verification for that flow in Auth0, but the “you must verify your email” detail that Auth0 sent back to ChatGPT isn’t displayed to the user. + +If you store User information in your own database, then the backend auth validation process is probably a two-step thing. First, validate the JWT and decode the claims, then look-up User row from information in the claims (sub/iss for auth0). One tip I’d offer to developers is think about whether you want one User account per email or one User account per auth mechanism, and separate out your Users and Principals tables appropriately. + +We definitely experienced some pain when users ended up logged into the Noteable UI app as one account (user/pass auth mechanism) and the ChatGPT plugin as a different account (say, google oauth social login) and then ran into weird permission errors trying to work with Projects or Notebooks owned by different accounts. We tried to lean on auth0’s built-in account linking but we’re working instead right now restructuring our database and sign-up flow so it is one account per email instead of auth mechanism. + +The final big unaddressed topic in that last diagram is account creation on the backend. It’s fine that ChatGPT or your frontend UI can redirect a new user to auth0 so they create an account in auth0, but how do you make sure there’s a matching account in your database? + +There’s two parts to that answer. First, you want to use the right OpenID connect scopes (https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes) so that Auth0 returns an id_token in addition to an access_token from the POST to the token_url. You may as well ask for a refresh_token while you’re at it. The scopes we put in our manifest file are “offline_access openid email profile”. + +Second, you need to receive the id_token somehow. The way we implemented that is to have ChatGPT POST to our plugin instead of directly to auth0 /oauth/token endpoint. Here’s the last diagram of this post. + +Thanks, I hope this helps. diff --git a/blog/authors.yml b/blog/authors.yml index b1d0f90..227e4d0 100644 --- a/blog/authors.yml +++ b/blog/authors.yml @@ -3,3 +3,9 @@ kyle: title: Mad Scientist @ Noteable url: https://github.com/rgbkrk image_url: https://github.com/rgbkrk.png + +kafonek: + name: Matt Kafonek + title: Senior Software Engineer @ Noteable + url: https://github.com/kafonek + image_url: https://github.com/kafonek.png