# Understanding the SDK Usage

This guide explains how the Humanity Connect SDK is used throughout the newsletter app to handle authentication and user data extraction.

## **SDK Initialization**

The SDK is initialized once and exported as a singleton in `src/lib/humanity-sdk.ts`:

```tsx
import { HumanitySDK } from '@humanity-org/connect-sdk'
import { getConfig } from './config'

let sdkInstance: HumanitySDK | null = null

export function getHumanitySdk(): HumanitySDK {
  if (sdkInstance) {
    return sdkInstance
  }

  const config = getConfig()

  sdkInstance = new HumanitySDK({
    clientId: config.humanity.clientId,
    redirectUri: config.humanity.redirectUri,
    environment: config.humanity.environment,
    clientSecret: config.humanity.clientSecret,
  })

  return sdkInstance
}
```

### **Key Configuration Parameters:**

* `clientId` - Your app's OAuth client ID (required)
* `redirectUri` - Where users are redirected after authorization (required)
* `environment` - Either `'sandbox'` or `'production'`
* `clientSecret` - Required for server-side token exchange

## **The OAuth Flow**

### **Step 1: Building the Authorization URL**

**File:** `src/app/api/auth/login/route.ts`

When a user clicks "Sign in with Humanity", the app calls the `/api/auth/login` endpoint which builds an authorization URL using the SDK:

```tsx
import { getHumanitySdk, HumanitySDK } from '@/lib/humanity-sdk'
import { saveOAuthSession } from '@/lib/session'

export async function POST() {
  const sdk = getHumanitySdk()

  // Generate PKCE state and nonce explicitly
  const state = HumanitySDK.generateState()
  const nonce = HumanitySDK.generateNonce()

  // Build authorization URL with SDK
  const { url, codeVerifier } = sdk.buildAuthUrl({
    scopes: ['openid', 'profile.full', 'data.read', 'identity:read'], // scopes requested
    state,
    nonce,
  })

  // Store PKCE parameters in session cookie
  saveOAuthSession(
    {
      clientId: config.humanity.clientId,
      redirectUri: config.humanity.redirectUri,
      environment: config.humanity.environment,
      scopes: ['openid', 'profile.full', 'data.read', 'identity:read'],
      codeVerifier, // PKCE code verifier - must be stored for later
      state, // Use our generated state
      nonce,
      createdAt: Date.now(),
    },
    request,
  )

  // Return authorization URL to frontend
  return NextResponse.json({ url })
}
```

#### **What the SDK Returns:**

* `url` - The full authorization URL to redirect the user to
* `codeVerifier` - PKCE parameter that **must be stored** and used during token exchange

**Important:** The `codeVerifier` must be stored securely (in session cookie) because it's required in the next step to exchange the authorization code for tokens.

### **Step 2: Handling the OAuth Callback**

**File:** `src/app/callback/route.ts`

After the user authorizes, Humanity redirects them back to `/callback` with an authorization code. The app exchanges this code for access tokens:

```tsx
import { getHumanitySdk, HumanitySDK } from '@/lib/humanity-sdk'
import { readOAuthSession, deleteOAuthSession } from '@/lib/session'
import { getAuthService } from '@/lib/auth-service'

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const code = searchParams.get('code')
  const callbackState = searchParams.get('state')

  // Read PKCE parameters from session
  const session = readOAuthSession(request)
  if (!session) {
    throw new Error('No OAuth session found')
  }

  // Verify state (CSRF protection)
  const statesMatch = HumanitySDK.verifyState(session.state, callbackState)

  if (!code || !callbackState || !statesMatch) {
    throw new Error('Invalid state or missing code')
  }

  const sdk = getHumanitySdk()

  // Exchange authorization code for tokens
  const tokens = await sdk.exchangeCodeForToken({
    code: code!,
    codeVerifier: session.codeVerifier, // From Step 1
  })

  // Tokens received:
  // - tokens.accessToken: Used to call Humanity APIs
  // - tokens.refreshToken: Used to get new access tokens
  // - tokens.idToken: Contains user identity claims

  const authService = getAuthService()

  // Extract user data from Humanity
  const userData = await authService.extractUserData(tokens.accessToken)

  // Create or update user in database
  const user = await authService.createOrUpdateUser(userData)

  // Issue app token
  const appToken = await authService.issueAppToken(user, userData.travelProfile)

  // Clean up OAuth session, create app session
  deleteOAuthSession(response)
  saveAppSession(
    {
      appToken: appToken.token,
      expiresAt: appToken.expiresAt.getTime(),
      userId: appToken.payload.appScopedUserId,
      humanityUserId: appToken.payload.humanityUserId,
      email: appToken.payload.email,
      evmAddress: appToken.payload.evmAddress,
      linkedSocials: appToken.payload.linkedSocials,
      presets: appToken.payload.presets,
      isFrequentTraveler: appToken.payload.isFrequentTraveler,
    },
    response,
  )

  // Redirect to feed
  return NextResponse.redirect(new URL('/feed', request.url))
}
```

#### **What the SDK Returns:**

* `accessToken` - Bearer token for calling Humanity APIs (short-lived)
* `refreshToken` - Token for obtaining new access tokens (long-lived)
* `idToken` - JWT containing basic identity claims

### **Step 3: Extracting User Data with the SDK**

**File:** `src/lib/auth-service.ts`

Once we have an access token, we use the SDK to fetch the user's email and verify their presets:

```tsx
import { getHumanitySdk } from './humanity-sdk'

export class AuthService {
  private readonly sdk = getHumanitySdk()

  async extractUserData(accessToken: string): Promise<ExtractedUserData> {
    const tokenPayload = this.decodeJwt(accessToken)

    // 1. Fetch email preset for user email
    const userProfile = await this.fetchEmailPreset(accessToken)

    // 2. Verify social account presets in batch
    const linkedSocials = await this.verifySocialAccounts(accessToken)

    // 3. Check travel profile using query engine
    const travelProfile = await this.checkTravelProfile(accessToken)

    // 4. Build presets list from social connections and travel
    const presets = this.buildPresetsList(linkedSocials, travelProfile)

    return {
      humanityUserId:
        (tokenPayload?.sub as string) || userProfile.humanityId || '',
      appScopedUserId:
        (tokenPayload?.app_scoped_user_id as string) ||
        (tokenPayload?.sub as string) ||
        '',
      email: userProfile.email || (tokenPayload?.email as string | undefined),
      evmAddress: userProfile.evmAddress,
      linkedSocials,
      presets,
      travelProfile,
      rawEvidence: { ...tokenPayload, ...userProfile.evidence },
    }
  }

  // 1. Fetch email preset for user email
  private async fetchEmailPreset(accessToken: string): Promise<{
    email?: string
    evmAddress?: string
    humanityId?: string
    evidence: Record<string, unknown>
  }> {
    try {
      const result = await this.sdk.verifyPreset({
        preset: 'email',
        accessToken,
      })

      const email =
        typeof result.value === 'string' ? result.value : result.evidence?.email

      const evidence = result.evidence || {}
      const evmAddress = result.evidence?.wallet_address
      const humanityId = result.evidence?.humanity_id

      return {
        email: email || undefined,
        evmAddress: evmAddress || undefined,
        humanityId: humanityId || undefined,
        evidence: evidence as Record<string, unknown>,
      }
    } catch {
      return { evidence: {} }
    }
  }

  // 2. Verify social connection presets in batch
  private async verifySocialAccounts(
    accessToken: string,
  ): Promise<SocialAccount[]> {
    const socials: SocialAccount[] = []
    const now = new Date()

    try {
      const batchResult = await this.sdk.verifyPresets({
        accessToken,
        presets: [
          'google_connected',
          'linkedin_connected',
          'twitter_connected',
          'discord_connected',
          'github_connected',
          'telegram_connected',
        ],
      })

      for (const preset of SOCIAL_PRESETS) {
        const result = batchResult.results?.find(
          (r: any) => r.presetName === preset || r.preset === preset,
        )
        const isConnected = result?.value === true
        const provider = SOCIAL_PRESET_TO_PROVIDER[preset]

        socials.push({
          provider: provider as SocialAccount['provider'],
          connected: isConnected,
          username: undefined,
          connectedAt: isConnected ? now : undefined,
        })
      }
    } catch {
      // Return empty socials on error
      for (const preset of SOCIAL_PRESETS) {
        const provider = SOCIAL_PRESET_TO_PROVIDER[preset]
        socials.push({
          provider: provider as SocialAccount['provider'],
          connected: false,
          username: undefined,
          connectedAt: undefined,
        })
      }
    }

    return socials
  }
}
```

#### **SDK Methods Used:**

1. **`sdk.verifyPreset({ preset: 'email', accessToken })`**
   * Returns single preset verification
   * The email preset returns the user's verified email as `value`
   * Additional data available in `evidence` object (wallet address, humanity ID)
2. **`sdk.verifyPresets({ accessToken, presets })`**
   * Batch verification of multiple presets
   * Returns array of `{ presetName, value }` objects
   * Used to detect which social accounts are connected

### **Step 4: Using the Query Engine for Complex Conditions**

**File:** `src/lib/auth-service.ts`

The SDK also provides a Query Engine for complex predicate queries:

```tsx
private async checkTravelProfile(accessToken: string): Promise<TravelProfile> {
  let hasHotelMembership = false
  let hasAirlineMembership = false

  try {
    // Run queries in parallel for better performance
    const [hotelResult, airlineResult] = await Promise.all([
      this.evaluateQuery(accessToken, HOTEL_MEMBERSHIP_QUERY),
      this.evaluateQuery(accessToken, AIRLINE_MEMBERSHIP_QUERY),
    ])

    hasHotelMembership = hotelResult
    hasAirlineMembership = airlineResult
  } catch {
    // Silently fail - travel profile is optional
  }

  return {
    hasHotelMembership,
    hasAirlineMembership,
    isFrequentTraveler: hasHotelMembership && hasAirlineMembership,
  }
}

private async evaluateQuery(accessToken: string, query: PredicateQuery): Promise<boolean> {
  try {
    const result = await this.sdk.evaluatePredicateQuery({
      accessToken,
      query,
    })
    return result.passed === true
  } catch {
    return false
  }
}
```

**Example Query for Hotel Membership:**

```tsx
const HOTEL_MEMBERSHIP_QUERY = {
  policy: {
    anyOf: [
      { check: { claim: 'membership.marriott', operator: 'isDefined' } },
      { check: { claim: 'membership.hilton', operator: 'isDefined' } },
      { check: { claim: 'membership.wyndham', operator: 'isDefined' } },
      { check: { claim: 'membership.radisson', operator: 'isDefined' } },
      { check: { claim: 'membership.shangri_la', operator: 'isDefined' } },
      { check: { claim: 'membership.taj_hotels', operator: 'isDefined' } },
      { check: { claim: 'membership.mgm_resorts', operator: 'isDefined' } },
      { check: { claim: 'membership.caesars', operator: 'isDefined' } },
      { check: { claim: 'membership.wynn_resorts', operator: 'isDefined' } },
      { check: { claim: 'membership.accor', operator: 'isDefined' } },
    ],
  },
}
```

#### **SDK Method:**

* **`sdk.evaluatePredicateQuery({ accessToken, query })`**
  * Evaluates complex conditional logic against user's credentials
  * Returns `{ passed: boolean }` indicating if conditions are met
  * Used for sophisticated eligibility checks

## **Key Files Where SDK is Used**

| File                              | Purpose                           | SDK Methods Used                                          |
| --------------------------------- | --------------------------------- | --------------------------------------------------------- |
| `src/lib/humanity-sdk.ts`         | SDK singleton initialization      | `new HumanitySDK()`, `getHumanitySdk()`                   |
| `src/app/api/auth/login/route.ts` | Build authorization URL           | `sdk.buildAuthUrl()`, `HumanitySDK.generateState()`       |
| `src/app/callback/route.ts`       | Exchange code for tokens          | `sdk.exchangeCodeForToken()`, `HumanitySDK.verifyState()` |
| `src/lib/auth-service.ts`         | Extract user data, verify presets | `sdk.verifyPreset()`, `sdk.verifyPresets()`               |
| `src/lib/auth-service.ts`         | Check travel preferences          | `sdk.evaluatePredicateQuery()`                            |

## **Data Flow Summary**

```
User clicks "Sign in"
         ↓
[login/route.ts] getHumanitySdk() + sdk.buildAuthUrl()
  → Generate state and nonce with HumanitySDK.generateState()
  → Returns: { url, codeVerifier }
  → Store codeVerifier, state, nonce in session cookie
  → Redirect user to authorization URL
         ↓
User authorizes on Humanity
         ↓
Humanity redirects to /callback?code=...&state=...
         ↓
[callback/route.ts] HumanitySDK.verifyState() + sdk.exchangeCodeForToken()
  → Verify state parameter matches session
  → Input: code, codeVerifier (from session)
  → Returns: { accessToken, refreshToken, idToken }
         ↓
[auth-service.ts] authService.extractUserData()
  → sdk.verifyPreset({ preset: 'email' })
    Returns: { value: email, evidence: { wallet_address, ... } }
  → sdk.verifyPresets() for social accounts
    Returns: { results: [{ presetName, value }, ...] }
  → sdk.evaluatePredicateQuery() for travel profile
    Returns: { passed: boolean }
         ↓
[auth-service.ts] authService.createOrUpdateUser()
  → Save user to MongoDB
         ↓
[auth-service.ts] authService.issueAppToken()
  → Create JWT with user claims and travel profile
         ↓
Save app session and redirect to personalized feed
```

## **Important Security Notes**

1. **Never expose `clientSecret` to the frontend** - it's only used in server-side API routes
2. **Always store `codeVerifier` securely** - required for PKCE token exchange
3. **Validate `state` parameter** - prevents CSRF attacks
4. **Use HTTP-only cookies** - prevents XSS access to session tokens
5. **Verify all tokens** - never trust client-provided data


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.humanity.org/developer-guides-and-tutorials/sdk-api-guides/personalized-newsletter-app-reference-implementation/understanding-the-sdk-usage.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
