Guides

End-to-End Tutorial

Set up a complete OpenApe environment with IdP, Service Provider, and Agent from scratch.

End-to-End Tutorial

This tutorial walks through setting up a complete OpenApe environment on your local machine: an Identity Provider (IdP), a Service Provider (SP) app, and an AI agent that requests grants.

What We'll Build

┌──────────────────┐     DDISA      ┌──────────────────┐
│   Service Provider│◄──────────────►│  Identity Provider│
│   (Nuxt App)      │   Login Flow   │  (Nuxt + IdP)    │
│   localhost:3001   │               │  localhost:3000    │
└──────────────────┘               └──────────────────┘
                                            ▲
                                            │ Grant Request
                                            │ + Approval
                                   ┌────────┴─────────┐
                                   │  AI Agent (CLI)   │
                                   │  grapes + escapes │
                                   └──────────────────┘

By the end, you'll have:

  1. An IdP that issues Passkey-based logins and manages grants
  2. An SP app that authenticates users via DDISA
  3. An enrolled agent that can request and use grants

Part 1: Set Up the IdP

Create the IdP project

npx nuxi@latest init my-idp
cd my-idp
npm install @openape/nuxt-auth-idp

Configure the IdP

Add the module to nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@openape/nuxt-auth-idp'],
  openapeIdp: {
    rpName: 'My OpenApe IdP',
    rpID: 'localhost',
    rpOrigin: 'http://localhost:3000',
    managementToken: 'dev-management-token-change-in-prod',
    grants: {
      enablePages: true
    }
  }
})

Start the IdP

npm run dev -- --port 3000

Verify it's running:

# JWKS endpoint
curl http://localhost:3000/.well-known/jwks.json

# OIDC discovery
curl http://localhost:3000/.well-known/openid-configuration

Part 2: Set Up the SP

In a separate terminal:

Create the SP project

npx nuxi@latest init my-sp
cd my-sp
npm install @openape/nuxt-auth-sp

Configure the SP

export default defineNuxtConfig({
  modules: ['@openape/nuxt-auth-sp'],
  openapeSp: {
    fallbackIdpUrl: 'http://localhost:3000'
  }
})

Add a Login Page

Create app/pages/index.vue:

<template>
  <div style="max-width: 400px; margin: 50px auto;">
    <h1>My App</h1>
    <div v-if="user">
      <p>Logged in as: <strong>{{ user.email }}</strong></p>
      <p>Type: {{ user.act }}</p>
      <button @click="logout()">Logout</button>
    </div>
    <OpenApeAuth v-else />
  </div>
</template>

<script setup>
const { user, logout } = useOpenApeAuth()
</script>

Start the SP

npm run dev -- --port 3001

Part 3: Install the CLI and Register a Human User

npm install -g @openape/grapes

Register and Login

grapes login --idp http://localhost:3000

This opens a browser where you register with a Passkey and log in.

In development on localhost, Passkeys use the device's built-in authenticator. On macOS, this uses Touch ID or iCloud Keychain.

Verify:

grapes whoami
# → alice@localhost (human) via http://localhost:3000

Part 4: Human Logs In to the SP

  1. Open http://localhost:3001 in your browser
  2. Enter alice@localhost in the login form
  3. Since there's no DNS record for localhost, the SP uses the fallback IdP (http://localhost:3000)
  4. Authenticate with your Passkey
  5. You're redirected back to the SP, logged in as alice@localhost

This is the complete DDISA flow: email → IdP discovery → Passkey auth → JWT → SP session.

Part 5: Enroll an Agent

Generate the agent's key and enroll

# Generate a key pair for the agent
ssh-keygen -t ed25519 -f /tmp/agent_key -N ""

# Enroll via the admin API (this still requires curl — it's an admin operation)
curl -X POST http://localhost:3000/api/agent/enroll \
  -H "Authorization: Bearer dev-management-token-change-in-prod" \
  -H "Content-Type: application/json" \
  -d "{
    \"email\": \"agent+worker@localhost\",
    \"name\": \"test-worker\",
    \"publicKey\": \"$(cat /tmp/agent_key.pub)\"
  }"

Login as the agent

grapes login --idp http://localhost:3000 --key /tmp/agent_key --email agent+worker@localhost
grapes whoami
# → agent+worker@localhost (agent) via http://localhost:3000

Part 6: Agent Requests a Grant

The agent requests permission to perform an action:

grapes request "echo Hello from OpenApe!" --audience escapes --reason "Testing the grant flow" --wait &

The --wait flag blocks until a human approves or denies. Check the IdP's grant dashboard at http://localhost:3000/grants — you'll see the pending request.

Part 7: Approve the Grant

In a separate terminal, login as the human approver and approve:

grapes login --idp http://localhost:3000

# See pending grants
grapes list --status pending

# Approve
grapes approve <grant-id>

The agent's grapes request --wait in the other terminal now completes — the grant is approved.

Use the grant with escapes

# Get the AuthZ-JWT
JWT=$(grapes token <grant-id>)

# Execute the approved command
escapes --grant "$JWT" -- echo Hello from OpenApe!

Or use grapes run for the one-liner version:

grapes run escapes "echo Hello from OpenApe!" --reason "Testing"
# → Requests grant, waits for approval, executes via escapes

What's Next?