@ atute/oauth-浏览器-客户端
aT 协议的最小 OAuth 浏览器客户端 .
npm install @atcute/oauth-browser-client
用于 Web 扩展,使用 @atcute/oauth-node-client 换句话说.
客户端元数据
您的应用程序需要 OAuth 客户端元数据文档, 托管在公共 URL 。 这是授权 关于您的应用程序的服务器 :
{
"client_id": "https://example.com/oauth-client-metadata.json",
"client_name": "My App",
"client_uri": "https://example.com",
"redirect_uris": ["https://example.com/oauth/callback"],
"scope": "atproto transition:generic",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"application_type": "web",
"dpop_bound_access_tokens": true
}
页:1 client_id 必须是此文档所在的 URL 。 见
OAuth 客户端元数据谱
用于所有可用的字段.
使用量
配置
调用 configureOAuth 使用此库中的任何其他功能之前 :
import { configureOAuth } from '@atcute/oauth-browser-client';
import {
CompositeDidDocumentResolver,
LocalActorResolver,
PlcDidDocumentResolver,
WebDidDocumentResolver,
XrpcHandleResolver,
} from '@atcute/identity-resolver';
configureOAuth({
metadata: {
client_id: 'https://example.com/oauth-client-metadata.json',
redirect_uri: 'https://example.com/oauth/callback',
},
identityResolver: new LocalActorResolver({
handleResolver: new XrpcHandleResolver({
serviceUrl: 'https://public.api.bsky.app',
}),
didDocumentResolver: new CompositeDidDocumentResolver({
methods: {
plc: new PlcDidDocumentResolver(),
web: new WebDidDocumentResolver(),
},
}),
}),
});
[注]
此示例使用 Bluesky 的 AppView 处理解析,因为web app缺乏直接的 DNS 访问权限. Bluesky可能按隐私政策记录处理决议 使用大小写.
开始授权
import { createAuthorizationUrl } from '@atcute/oauth-browser-client';
const authUrl = await createAuthorizationUrl({
target: { type: 'account', identifier: 'mary.my.id' },
scope: 'atproto transition:generic transition:chat.bsky',
});
await sleep(200); // let browser persist local storage
window.location.assign(authUrl);
最终授权
在您重新定向的 URL 上,提取参数并最后确定 :
import { Client } from '@atcute/client';
import { OAuthUserAgent, finalizeAuthorization } from '@atcute/oauth-browser-client';
// server redirects with params in hash, not search string
const params = new URLSearchParams(location.hash.slice(1));
// scrub params from URL to prevent replay
history.replaceState(null, '', location.pathname + location.search);
const { session } = await finalizeAuthorization(params);
const agent = new OAuthUserAgent(session);
const rpc = new Client({ handler: agent });
const { data } = await rpc.get('com.atproto.identity.resolveHandle', {
params: { handle: 'mary.my.id' },
});
这段会话在内部持续,不要存放在别处. 田径公司签了字 您的用户界面, 因为没有刷新符号的会话可能会过期 .
续会
import { OAuthUserAgent, getSession } from '@atcute/oauth-browser-client';
const session = await getSession('did:plc:ia76kvnndjutgedggx2ibrem', {
allowStale: true,
});
const agent = new OAuthUserAgent(session);
签名退出
import { OAuthUserAgent, deleteStoredSession, getSession } from '@atcute/oauth-browser-client';
const did = 'did:plc:ia76kvnndjutgedggx2ibrem';
try {
const session = await getSession(did, { allowStale: true });
const agent = new OAuthUserAgent(session);
await agent.signOut();
} catch {
deleteStoredSession(did); // fallback if signOut fails
}
保密客户端模式
默认情况下,此库将作为 公共客户端 会期较短。 页:1 设立为期较长的会议 客户端断言后端 启用 保密客户端模式。 。 。 .
添加 fetchClientAssertion 给你的配置。 后端 API 完全由您决定 - 这只是一个
示例:
configureOAuth({
// ... existing config
async fetchClientAssertion({ aud, createDpopProof }) {
const htu = 'https://example.com/api/client-assertion';
const dpop = await createDpopProof(htu);
const response = await fetch(htu, {
method: 'POST',
headers: { dpop, 'content-type': 'application/json' },
body: JSON.stringify({ aud }),
});
const data = await response.json();
return {
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_assertion: data.assertion,
};
},
});
您的后端验证 dpop 证明并签名客户端断言 jwt 包含 iss, (中文(简体) ), sub (两者)
你的客户身份, aud (授权服务器), exp, (中文(简体) ), jti (独特的非ce)和 cnf: { jkt } (单位:千美元)
允许从证据中生成密钥拇指印).
为保密模式更新客户端元数据 - 替换 token_endpoint_auth_method 与
private_key_jwt,添加 token_endpoint_auth_signing_alg: "ES256",并添加一个 jwks_uri 指点
你的公用钥匙.
vite 本地发展
AT 协议 OAuth 禁止 localhost - 使用时 127.0.0.1 相反:
// vite.config.ts
import { defineConfig } from 'vite';
import metadata from './public/oauth-client-metadata.json' with { type: 'json' };
const SERVER_HOST = '127.0.0.1';
const SERVER_PORT = 12520;
export default defineConfig({
server: { host: SERVER_HOST, port: SERVER_PORT },
plugins: [
{
name: 'oauth-envs',
config(_conf, { command }) {
if (command === 'build') {
process.env.VITE_OAUTH_CLIENT_ID = metadata.client_id;
process.env.VITE_OAUTH_REDIRECT_URI = metadata.redirect_uris[0];
} else {
const redirectUri = `http://${SERVER_HOST}:${SERVER_PORT}${new URL(metadata.redirect_uris[0]).pathname}`;
process.env.VITE_OAUTH_CLIENT_ID =
`http://localhost?redirect_uri=${encodeURIComponent(redirectUri)}` +
`&scope=${encodeURIComponent(metadata.scope)}`;
process.env.VITE_OAUTH_REDIRECT_URI = redirectUri;
}
process.env.VITE_OAUTH_SCOPE = metadata.scope;
},
},
],
});
然后在代码中使用环境变量 :
configureOAuth({
metadata: {
client_id: import.meta.env.VITE_OAUTH_CLIENT_ID,
redirect_uri: import.meta.env.VITE_OAUTH_REDIRECT_URI,
},
// ...
});
说明
- 极少执行: 只有 ES256 DPop 密钥, 需要 PKCE 和 DPop 绑定 PAR
- 无索引数据库:在Safari锁定模式下工作,但不能使用不可导出密钥作为 建议 由 DPoP 光谱
- 有限测试:在个人项目中工作,但考虑 参考文献 执行 用于生产