A minimal starter for ATProto logins in Astro
1---
2import '../styles.css'
3import { getSession } from '../lib/session'
4import { getOAuthClient } from '../lib/context'
5import { Agent } from '@atproto/api'
6
7const session = getSession(Astro.cookies)
8const oauthClient = getOAuthClient(Astro.cookies)
9
10let agent: Agent | null = null
11let profile: any = null
12
13if (session.did) {
14 try {
15 const oauthSession = await oauthClient.restore(session.did)
16 if (oauthSession) {
17 agent = new Agent(oauthSession)
18
19 try {
20 const profileResponse = await agent.app.bsky.actor.getProfile({
21 actor: agent.assertDid,
22 })
23 profile = profileResponse.data
24 } catch (err) {
25 console.warn('Failed to fetch profile:', err)
26 }
27 }
28 } catch (err) {
29 console.warn('OAuth restore failed:', err)
30 session.destroy()
31 }
32}
33
34const displayName = profile?.displayName || agent?.assertDid || 'User'
35const handle = profile?.handle || agent?.assertDid || ''
36const avatar = profile?.avatar
37const description = profile?.description
38---
39
40<html lang="en" data-theme="dracula">
41 <head>
42 <meta charset="utf-8" />
43 <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
44 <meta name="viewport" content="width=device-width" />
45 <meta name="generator" content={Astro.generator} />
46 <title>ATProto Login</title>
47 </head>
48 <body>
49 <div class="min-h-screen flex items-center justify-center">
50 <div class="card w-96 bg-base-200 shadow-xl p-8">
51 <div class="card-body">
52 {
53 agent ? (
54 <>
55 <h2 class="card-title justify-center mb-4">Welcome!</h2>
56 <div class="space-y-4">
57 <div class="flex flex-col items-center text-center">
58 {avatar && (
59 <div class="avatar mb-4">
60 <div class="w-24 rounded-full">
61 <img src={avatar} alt={displayName} />
62 </div>
63 </div>
64 )}
65 <p class="text-lg font-semibold">{displayName}</p>
66 <p class="text-sm opacity-70">{handle}</p>
67 {description && (
68 <p class="text-sm mt-2 opacity-80">{description}</p>
69 )}
70 </div>
71 <form method="POST" action="/api/logout" class="w-full">
72 <button type="submit" class="btn btn-error w-full">
73 Logout
74 </button>
75 </form>
76 </div>
77 </>
78 ) : (
79 <>
80 <h2 class="card-title justify-center mb-6">ATProto Login</h2>
81 <form method="POST" action="/api/login">
82 <div class="join join-vertical w-full">
83 <input
84 type="text"
85 placeholder="Enter your handle (e.g. alice.bsky.social)"
86 class="input input-bordered join-item w-full"
87 name="handle"
88 required
89 />
90 <button type="submit" class="btn btn-primary join-item w-full">
91 Login
92 </button>
93 </div>
94 </form>
95 </>
96 )
97 }
98 </div>
99 </div>
100 </div>
101 </body>
102</html>