diff --git a/docs/src/content/docs/oauth/meta.json b/docs/src/content/docs/oauth/meta.json index 809a69c2..27996064 100644 --- a/docs/src/content/docs/oauth/meta.json +++ b/docs/src/content/docs/oauth/meta.json @@ -1,5 +1,5 @@ { "title": "OAuth Providers", - "pages": ["github", "bitbucket", "figma", "discord", "gitlab", "spotify"], + "pages": ["github", "bitbucket", "figma", "discord", "gitlab", "spotify", "x"], "defaultOpen": false } diff --git a/docs/src/content/docs/oauth/x.mdx b/docs/src/content/docs/oauth/x.mdx new file mode 100644 index 00000000..b18905d7 --- /dev/null +++ b/docs/src/content/docs/oauth/x.mdx @@ -0,0 +1,144 @@ +--- +title: X (Twitter) Authorization Provider +description: Configure the X OAuth 2.0 provider in Aura Auth for authentication and authorization. +--- + +## X (Twitter) + +Set up the `X` authorization provider for the authentication instance in Aura Auth. + +--- + +## What you'll learn + +Through this quick start guide, you will learn and understand the basics of how to set up the `X` provider for Aura Auth. + +- [X OAuth App](#x-oauth-app) + - [Creating an OAuth App](#creating-a-x-oauth-application) +- [X Aura Auth](#x-aura-auth) + - [Installation](#installation) + - [Environment setup](#environment-setup) + - [Configure the provider](#configure-the-provider) + - [Get HTTP handlers](#get-http-handlers) +- [Resources](#resources) + +--- + + + + + +## X OAuth App + +### Creating an X OAuth Application + +The first step is to create and register an [`X App`](https://developer.x.com/en/portal/projects-and-apps) to grant access to the user's accessible resources like `Get my User` (used by Aura Auth), `Posts`, `Likes`, `Lists`, etc. For more detailed information, read the [X Developer Portal](https://developer.x.com/en/portal/dashboard), [Get my User](https://docs.x.com/x-api/users/get-my-user), and [OAuth 2.0 Scopes](https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code#scopes). + +Creating an X OAuth app includes: + +- `App name`: X's app name. +- `App permissions`: Permissions available to the X app. The options include: + - `Read`. + - `Read and write` (Recommended). + - `Read and write and Direct message`. +- `Type of App`: Enables the app to work with OAuth 2.0 authentication. + - `Native App`: Public client with the OAuth 2.0 authorization code flow that implements PKCE. For more details, read [PKCE](https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code). This is the option supported by Aura Auth. + - `Web App, Automated App or Bot`: Confidential client, which implements the `client_credentials` grant type in the authorization. For more details, read [Application only](https://docs.x.com/fundamentals/authentication/oauth-2-0/application-only). +- `Callback URI`: The URL to which X OAuth will redirect. It should end in `/auth/callback/x` for local and production environments. + - Local environment: `http://localhost:3000/auth/callback/x`. + - Production environment: Set the URL of your production application. +- `Website URL (Required)`: The URL of the app. +- `Organization Name (Optional)`: The application name shown when the user tries to grant access to the app. +- `Organization URL (Optional)`: The link shown when users authorize your app. +- `Terms of service (Required)`: The link to the terms of service shown to the user. +- `Privacy policy (Required)`: The link to the privacy policy shown to the user. + + + + + +## X Aura Auth + +### Installation + +Install the package using a package manager like `npm`, `pnpm` or `yarn`. + +```npm +npm install @aura-stack/auth +``` + + + + + +### Environment setup + +Now, it's time to set up the X credentials required by Aura Auth, which include the `client Id` and `client Secret`, and write them into a `.env` file. + +Additionally set the `secret` used by Aura Auth to sign and encrypt the user's session. + +```bash title=".env" lineNumbers +# X Credentials +AURA_AUTH_X_CLIENT_ID="x_client_id" +AURA_AUTH_X_CLIENT_SECRET="x_client_secret" + +# Aura Secret +AURA_AUTH_SECRET="32-bytes-secret" +``` + + + The `AURA_AUTH_SECRET` is recommended to be a random and high entropy key to avoid attackers deciphering the secret used by the + Aura Auth application. + + + + + + +### Configure the provider + +Set the `oauth` option of the `createAuth` instance by writing the `"x"` provider name. + +```ts title="@/auth" lineNumbers +import { createAuth } from "@aura-stack/auth" + +export const auth = createAuth({ + oauth: ["x"], +}) + +export const { handlers } = auth +``` + + + + + +### Get HTTP Handlers + +Use the HTTP handlers to consume the authentication logic and flow from the Aura Auth library to integrate it into routers and frameworks. + +```ts title="backend.ts" lineNumbers +import { handlers } from "@/auth" + +export const { GET, POST } = handlers +``` + + + The returned handlers include pre-built routes used in OAuth flows (`/signIn/:oauth`, `/callback/:oauth`, `/session`, `/signOut` + and `/csrfToken`). You can mount them in Express, Hono, Next.js, or any runtime that supports native Request and Response APIs. + + + + + + +--- + +## Resources + +- [RFC - The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749) +- [X - Developer Portal](https://developer.x.com/en/portal/projects-and-apps) +- [X - Get my User](https://docs.x.com/x-api/users/get-my-user) +- [X - OAuth 2.0 Authorization Code Flow with PKCE](https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code) +- [X - OAuth 2.0 Scopes](https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code#scopes) +- [X - OAuth 2.0 Bearer Token](https://docs.x.com/fundamentals/authentication/oauth-2-0/application-only) diff --git a/packages/core/src/actions/callback/access-token.ts b/packages/core/src/actions/callback/access-token.ts index dd5f80a5..03abef17 100644 --- a/packages/core/src/actions/callback/access-token.ts +++ b/packages/core/src/actions/callback/access-token.ts @@ -25,10 +25,11 @@ export const createAccessToken = async ( } const { accessToken, clientId, clientSecret, code: codeParsed, redirectURI: redirectParsed } = parsed.data try { - const response = await fetch(accessToken, { + const request = new Request(accessToken, { method: "POST", headers: { Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ @@ -40,6 +41,7 @@ export const createAccessToken = async ( code_verifier: codeVerifier, }).toString(), }) + const response = await fetch(request) const json = await response.json() const token = OAuthAccessTokenResponse.safeParse(json) if (!token.success) { diff --git a/packages/core/src/oauth/index.ts b/packages/core/src/oauth/index.ts index e9e6e3e7..ffc7bcd5 100644 --- a/packages/core/src/oauth/index.ts +++ b/packages/core/src/oauth/index.ts @@ -10,6 +10,7 @@ import { figma } from "./figma.js" import { discord } from "./discord.js" import { gitlab } from "./gitlab.js" import { spotify } from "./spotify.js" +import { x } from "./x.js" export { github } from "./github.js" export { bitbucket } from "./bitbucket.js" @@ -17,6 +18,7 @@ export { figma } from "./figma.js" export { discord } from "./discord.js" export { gitlab } from "./gitlab.js" export { spotify } from "./spotify.js" +export { x } from "./x.js" export const integrations = { github, @@ -25,6 +27,7 @@ export const integrations = { discord, gitlab, spotify, + x, } const defineOAuthEnvironment = (oauth: string) => { diff --git a/packages/core/src/oauth/x.ts b/packages/core/src/oauth/x.ts new file mode 100644 index 00000000..d701ca4d --- /dev/null +++ b/packages/core/src/oauth/x.ts @@ -0,0 +1,38 @@ +import type { OAuthConfig } from "@/@types/index.js" + +/** + * @see [X - Get my User](https://docs.x.com/x-api/users/get-my-user) + */ +export interface XProfile { + data: { + id: string + name: string + username: string + profile_image_url: string + } +} + +/** + * @see [X - Developer Portal](https://developer.x.com/en/portal/projects-and-apps) + * @see [X - Get my User](https://docs.x.com/x-api/users/get-my-user) + * @see [X - OAuth 2.0 Authorization Code Flow with PKCE](https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code) + * @see [X - OAuth 2.0 Scopes](https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code#scopes) + * @see [X - OAuth 2.0 Bearer Token](https://docs.x.com/fundamentals/authentication/oauth-2-0/application-only) + */ +export const x: OAuthConfig = { + id: "x", + name: "X", + authorizeURL: "https://x.com/i/oauth2/authorize", + accessToken: "https://api.x.com/2/oauth2/token", + userInfo: "https://api.x.com/2/users/me?user.fields=profile_image_url", + scope: "users.read users.email tweet.read offline.access", + responseType: "code", + profile({ data }) { + return { + sub: data.id, + name: data.name, + image: data.profile_image_url, + email: "", + } + }, +}