Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions docs/src/content/docs/oauth/coinbase.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
---
title: Coinbase Authorization Provider
description: Add Coinbase authorization provider to Aura Auth to authentication and authorize
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Grammar fix in frontmatter description.

🔧 Proposed fix
-description: Add Coinbase authorization provider to Aura Auth to authentication and authorize
+description: Add the Coinbase authorization provider to Aura Auth to authenticate and authorize users
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: Add Coinbase authorization provider to Aura Auth to authentication and authorize
description: Add the Coinbase authorization provider to Aura Auth to authenticate and authorize users
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/content/docs/oauth/coinbase.mdx` at line 3, Update the frontmatter
"description" value in the coinbase.mdx file to correct the grammar; replace
"Add Coinbase authorization provider to Aura Auth to authentication and
authorize" with a grammatically correct phrase such as "Add Coinbase
authorization provider to Aura Auth to authenticate and authorize users" so the
description uses the verbs "authenticate" and "authorize" (reference the
frontmatter "description" field in coinbase.mdx).

---

## Coinbase

Set up the `Coinbase` authorization provider in your Aura Auth instance.

---

## What you'll build

Through this quick start guide you are going to learn and understand the basics and how to set up the `Coinbase` provider for Aura Auth.

- [Coinbase OAuth App](#coinbase-oauth-app)
- [Installation](#installation)
- [Environment setup](#environment-setup)
- [Configure the Auth Instance](#configure-the-auth-instance)
- [Customizing the OAuth Provider](#customizing-the-oauth-provider)
- [Sign In to Coinbase (Client & Server)](#sign-in-to-coinbase-client--server)
- [Resources](#resources)

---

<Steps>

<Step>

## Coinbase OAuth App

### Register the Application

The first step is to create and register an OAuth App on the Coinbase CDP Portal to obtain access to the user's resources.

1. Navigate to your Coinbase profile and go to **API Keys > Secret API Keys**.
2. Click **Create API Key**.
3. Fill in the "API Key nickname".
4. Register the API restrictions:
- IP allowlist: `localhost:3000` (or your production domain when deploying)

5. Set the **Authorization callback URL** to `http://localhost:3000/auth/callback/coinbase`.
- _(Make sure to replace `localhost:3000` with your production domain when deploying)_
6. Click **Register application**.
7. Ensure you copy the **Client ID** and click **Generate a new client secret**.

</Step>

<Step>

## Installation

Install the package using a package manager like `npm`, `pnpm`, or `yarn`:

```npm
npm install @aura-stack/auth
```

</Step>

<Step>

## Environment setup

Now, you must configure the environment variables required by Aura Auth, including the Coinbase credentials and the encryption secrets.

```bash title=".env" lineNumbers
# Aura Secrets
AURA_AUTH_SECRET="your-32-byte-secret"
AURA_AUTH_SALT="your-32-byte-salt"

# Coinbase Credentials
AURA_AUTH_COINBASE_CLIENT_ID="your_coinbase_client_id"
AURA_AUTH_COINBASE_CLIENT_SECRET="your_coinbase_client_secret"
```

<Callout type="warn">
**CRITICAL SECURITY WARNING:** The `AURA_AUTH_SECRET` and `AURA_AUTH_SALT` variables are used to encrypt and sign user sessions.
These MUST be securely generated, highly randomized strings consisting of at least 32 bytes to ensure adequate entropy. Never
hardcode these values in your repository. Use a secure generator (like `openssl rand -base64 32`) to create them, and store them
exclusively in your secure environment variables manager.
</Callout>

</Step>

<Step>

## Configure the Auth Instance

Configure the `createAuth` instance inside an `auth.ts` file located at the root of your project. Ensure you explicitly export the `handlers`, `api`, and `jose` objects.

```ts title="auth.ts" lineNumbers
import { createAuth } from "@aura-stack/auth"

export const auth = createAuth({
oauth: ["coinbase"],
})

// Extract the required utilities
export const { handlers, api, jose } = auth
```

<Callout type="info">
The `handlers` object contains mapping utilities for standard HTTP methods (`GET`, `POST`, `PATCH`) as well as a unified `ALL`
handler. This allows you to easily mount the authentication routes across any framework (Next.js, Elysia, Express, etc.).
</Callout>

</Step>

<Step>

## Customizing the OAuth Provider

If you need to define custom scopes, change the response type, or map profile data differently, you can use the provider's factory function instead of a simple string identifier.

```ts title="auth.ts" lineNumbers
import { createAuth } from "@aura-stack/auth"
import { coinbase } from "@aura-stack/auth/oauth/coinbase"

export const auth = createAuth({
oauth: [
coinbase({
authorize: {
params: {
// Override default scopes
scope: "wallet:user:read wallet:user:email",
},
},
}),
],
})
Comment thread
halvaradop marked this conversation as resolved.

export const { handlers, api, jose } = auth
```

</Step>

<Step>

## Sign In to Coinbase (Client & Server)

There are multiple ways to trigger the sign-in flow depending on your ecosystem.

### Sign-in Path (Direct Navigation)

The common route to trigger the auth flow natively without needing a client library is simply navigating the browser to:
`http://localhost:3000/auth/signIn/coinbase`

---

### Client-Side (React, Vue, etc.)

You can utilize the `createAuthClient` utility to programmatically trigger sign-ins. You can also define a `redirectTo` destination.

<Callout type="warn">
**Constraint Rule**: The `baseURL` passed into `createAuthClient` MUST exactly match the root domain and path where the HTTP
`handlers` expose their endpoints on the server.
</Callout>

```ts title="components/Login.tsx" lineNumbers
import { createAuthClient } from "@aura-stack/auth/client"

export const authClient = createAuthClient({
baseURL: "http://localhost:3000/auth",
})

const triggerSignIn = async () => {
await authClient.signIn("coinbase", {
redirectTo: "/dashboard",
})
}
```

---

### Server-Side (Next.js Actions, Remix Loaders, etc.)

For environments supporting server-side actions, use the programmatic `api.signIn` method securely.

```ts title="actions.ts" lineNumbers
import { api } from "./auth"

export const serverSignIn = async () => {
const response = await api.signIn("coinbase", {
redirectTo: "http://localhost:3000/dashboard",
})

// Example returning redirect location
return response.headers.get("Location")
}
```
Comment thread
halvaradop marked this conversation as resolved.

---

### Session Retrieval

After a user successfully signs in, you can retrieve their session data securely.

**Client-Side:**

```ts
const session = await authClient.getSession()
console.log(session?.user) // The authenticated Coinbase user profile
```

**Server-Side:**

```ts
// Note: You must pass the native Web Request object or Headers!
const session = await api.getSession(request)
console.log(session?.user) // Safely retrieved backend session
```

</Step>

</Steps>

---

## Resources

- [RFC - The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749)
- [Coinbase - App OAuth2 Integration](https://docs.cdp.coinbase.com/coinbase-app/oauth2-integration/integrations)
- [Coinbase - Scopes](https://docs.cdp.coinbase.com/coinbase-app/oauth2-integration/scopes)
48 changes: 48 additions & 0 deletions packages/core/src/oauth/coinbase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { OAuthProviderCredentials, User } from "@/@types/index.ts"

export interface CoinbaseProfile {
data: {
id: string
name: string
username: string
profile_bio: string | null
profile_location: string | null
profile_url: string
avatar_url: string
resource: string
resource_path: string
}
}

/**
* Coinbase OAuth Provider
*
* @see [Coinbase - App OAuth2 Integration](https://docs.cdp.coinbase.com/coinbase-app/oauth2-integration/integrations)
* @see [Coinbase - Scopes](https://docs.cdp.coinbase.com/coinbase-app/oauth2-integration/scopes)
*/
export const coinbase = <DefaultUser extends User = User>(
options?: Partial<OAuthProviderCredentials<CoinbaseProfile, DefaultUser>>
): OAuthProviderCredentials<CoinbaseProfile, DefaultUser> => {
return {
id: "coinbase",
name: "Coinbase",
authorize: {
url: "https://login.coinbase.com/oauth2/auth",
params: {
scope: "wallet:user:read+wallet:user:email",
Comment thread
halvaradop marked this conversation as resolved.
responseType: "code",
},
},
accessToken: "https://login.coinbase.com/oauth2/token",
userInfo: "https://api.coinbase.com/v2/user",
profile: (profile) => {
return {
sub: String(profile.data.id),
name: profile.data.name,
image: profile.data.avatar_url,
email: null,
} as DefaultUser
},
Comment thread
halvaradop marked this conversation as resolved.
...options,
}
Comment on lines +26 to +47
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# How do existing providers handle partial option merging?
fd -e ts . packages/core/src/oauth --exec rg -nH -C2 '\.\.\.options' {}

Repository: aura-stack-ts/auth

Length of output: 4630


🏁 Script executed:

cat packages/core/src/oauth/coinbase.ts

Repository: aura-stack-ts/auth

Length of output: 1642


🏁 Script executed:

head -150 docs/src/content/docs/oauth/coinbase.mdx | tail -30

Repository: aura-stack-ts/auth

Length of output: 641


🏁 Script executed:

rg -nH 'OAuthProviderCredentials' packages/core/src/@types --max-count=5

Repository: aura-stack-ts/auth

Length of output: 714


🏁 Script executed:

cat packages/core/src/@types/index.ts | head -100

Repository: aura-stack-ts/auth

Length of output: 1932


🏁 Script executed:

cat packages/core/src/@types/oauth.ts

Repository: aura-stack-ts/auth

Length of output: 2800


🏁 Script executed:

fd -e test -e spec . packages/core --type f | head -20

Repository: aura-stack-ts/auth

Length of output: 44


🏁 Script executed:

rg -l "coinbase" --type ts packages/ | grep -i test

Repository: aura-stack-ts/auth

Length of output: 44


🏁 Script executed:

cat packages/core/src/oauth/discord.ts

Repository: aura-stack-ts/auth

Length of output: 3591


🏁 Script executed:

cat packages/core/src/oauth/github.ts

Repository: aura-stack-ts/auth

Length of output: 2860


This is a systemic issue affecting all OAuth providers, not just Coinbase.

The shallow spread pattern with ...options is used by all 16+ providers (GitHub, Discord, Spotify, etc.). When users follow the documented pattern and pass a partial authorize object (e.g., { authorize: { params: { scope: "..." } } }), it replaces the entire authorize object, silently losing the url and responseType. This breaks the OAuth flow.

The fix should apply uniformly across all providers—either deep-merge authorize/accessToken/userInfo fields, or update documentation to clarify that overrides must be complete objects. The current approach creates a footgun where the documented usage pattern in docs/src/content/docs/oauth/coinbase.mdx (lines 122-129) would silently fail.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/oauth/coinbase.ts` around lines 26 - 48, The provider
factory currently returns a default provider object and then shallow-spreads
...options which will completely overwrite nested objects like authorize,
accessToken, and userInfo if the caller supplies partial overrides; update the
factory to deep-merge those nested fields instead of shallow-spreading so
callers can pass partial authorize.params without losing url/responseType.
Specifically, in the Coinbase provider (and apply across all providers) merge
defaults.authorize with options.authorize (and their params sub-objects), and
likewise merge defaults.accessToken and defaults.userInfo with
options.accessToken/options.userInfo before returning the provider object so
that symbols authorize, authorize.params, accessToken, and userInfo are
preserved and extended rather than replaced.

}
3 changes: 3 additions & 0 deletions packages/core/src/oauth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { notion } from "./notion.ts"
import { dropbox } from "./dropbox.ts"
import { atlassian } from "./atlassian.ts"
import { clickUp } from "./click-up.ts"
import { coinbase } from "./coinbase.ts"
import { formatZodError } from "@/shared/utils.ts"
import { AuthInternalError } from "@/shared/errors.ts"
import { OAuthEnvSchema, OAuthProviderCredentialsSchema } from "@/schemas.ts"
Expand All @@ -39,6 +40,7 @@ export * from "./notion.ts"
export * from "./dropbox.ts"
export * from "./atlassian.ts"
export * from "./click-up.ts"
export * from "./coinbase.ts"

export const builtInOAuthProviders = {
github,
Expand All @@ -56,6 +58,7 @@ export const builtInOAuthProviders = {
dropbox,
atlassian,
clickUp,
coinbase,
} as const

/**
Expand Down
Loading