this repo has no description
1# Expo Atproto OAuth
2
3This is an Expo client library for Atproto OAuth. It implements the required native crypto functions for supporting JWTs in React Native and uses
4the base `OAuthClient` interface found in [the Atproto repository](https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client).
5
6## Prerequisites
7
8Before using this library, there are a few additional libraries that you must install within your Expo application.
9
10- [react-native-mmkv](https://www.npmjs.com/package/react-native-mmkv)
11- [expo-web-browser](https://www.npmjs.com/package/expo-web-browser)
12- [@atproto/oauth-client](https://www.npmjs.com/package/@atproto/oauth-client)
13- [event-target-polyfill](https://www.npmjs.com/package/event-target-polyfill) (or similar)
14- [abortcontroller-polyfill](https://www.npmjs.com/package/abortcontroller-polyfill) (or similar)
15
16Apply the two polyfills inside your application's entrypoint (usually `index.ts`). They should be placed _before_ anything else in the file, and particularly before `registerRootComponent(App)`.
17
18> [!CAUTION]
19> As of current (Expo 53), you _must_ apply an Expo patch for this library to work. You may use the patch found [here](https://github.com/haileyok/expo-atproto-auth/blob/main/patches/expo%2B53.0.19.patch).
20A fix for this has been submitted up stream and merged, so will hopefully be fixed in Expo 54 (see the PR [here](https://github.com/expo/expo/pull/38122)).
21
22### In bare React Native projects
23
24For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/)
25before continuing.
26
27## Installation
28
29Once you have satisfied the prerequisites, you can simply install the library with `yarn add expo-atproto-auth`.
30
31## Usage
32
33### Serve your `oauth-client-metadata.json`
34
35You will need to server an `oauth-client-metadata.json` from your application's website. An example of this metadata
36would look like this:
37
38```
39{
40 "client_id": "https://hailey.at/oauth-client-metadata.json",
41 "client_name": "React Native OAuth Client Demo",
42 "client_uri": "https://hailey.at",
43 "redirect_uris": [
44 "at.hailey:/auth/callback"
45 ],
46 "scope": "atproto transition:generic",
47 "token_endpoint_auth_method": "none",
48 "response_types": [
49 "code"
50 ],
51 "grant_types": [
52 "authorization_code",
53 "refresh_token"
54 ],
55 "application_type": "native",
56 "dpop_bound_access_tokens": true
57}
58```
59
60- The `client_id` should be the same URL as where you are serving your `oauth-client-metadata.json` from
61- The `client_uri` can be the home page of where you are serving your metadata from
62- Your `redirect_uris` should contain the native redirect URI in the first position. Additionally, the scheme must be
63formatted as the _reverse_ of the domain you are serving the metadata from. Since I am serving mine from `hailey.at`,
64I use `at.hailey` as the scheme. If my domain were `atproto.expo.dev`, I would use `dev.expo.atproto`. Additionally, the scheme _must_ contain _only one trailing slash_ after the `:`. `at.hailey://` would be invalid.
65- The `application_type` must be `native`
66
67For a real-world example, see [Skylight's client metadata](https://skylight.expo.app/oauth/client-metadata.json).
68
69For more information about client metadata, see [the Atproto documentation](https://atproto.com/specs/oauth#client-id-metadata-document).
70
71### Create a client
72
73Next, you want to create an `ExpoOAuthClient`. You will need to pass in the same client metadata to the client as you are serving in your `oauth-client-metadata.json`.
74
75```ts
76const client = new ExpoOAuthClient({
77 clientMetadata: {
78 client_id: 'https://hailey.at/oauth-client-metadata.json',
79 client_name: 'React Native OAuth Client Demo',
80 client_uri: 'https://hailey.at',
81 redirect_uris: ['at.hailey:/auth/callback'],
82 scope: 'atproto transition:generic',
83 token_endpoint_auth_method: 'none',
84 response_types: ['code'],
85 grant_types: ['authorization_code', 'refresh_token'],
86 application_type: 'native',
87 dpop_bound_access_tokens: true,
88 },
89 handleResolver: 'https://bsky.social',
90})
91```
92
93### Sign a user in
94
95Whenever you are ready, you can initiate a sign in attempt for the user using the client using `client.signIn(input)`
96
97`input` must be one of the following:
98- A valid Atproto user handle, e.g. `hailey.bsky.team` or `hailey.at`
99- A valid DID, e.g. `did:web:hailey.at` or `did:plc:oisofpd7lj26yvgiivf3lxsi`
100- A valid PDS host, e.g. `https://cocoon.hailey.at` or `https://bsky.social`
101
102> [!NOTE]
103> If you wish to allow a user to _create_ an account instead of signing in, simply use a valid PDS hostname rather than
104> a handle. They will be presented the option to either Sign In with an existing account, or create a new one.
105
106The response of `signIn` will be a promise resolving to the following:
107
108```ts
109 | { status: WebBrowserResultType } // See Expo Web Browser documentation
110 | { status: 'error'; error: unknown }
111 | { status: 'success'; session: OAuthSession }
112```
113
114For example:
115
116```ts
117 const res = await client.signIn(input ?? '')
118 if (res.status === 'success') {
119 setSession(res.session)
120 const newAgent = new Agent(res.session)
121 setAgent(newAgent)
122 } else if (res.status === 'error') {
123 Alert.alert('Error', (res.error as any).toString())
124 } else {
125 Alert.alert(
126 'Error',
127 `Received unknown WebResultType: ${res.status}`
128 )
129 }
130```
131
132### Create an `Agent`
133
134To interface with the various Atproto APIs, you will need to create an `Agent`. You will pass your `OAuthSession` to the `Agent`.
135
136```ts
137const newAgent = new Agent(res.session)
138```
139
140Session refreshes will be handled for you for the lifetime of the agent.
141
142### Restoring a session
143
144After, for example, closing the application, you will probably need to restore the user's session. You can do this by using the user's DID on the `ExpoOAuthClient`.
145
146```ts
147const restoreRes = await client.restore('did:plc:oisofpd7lj26yvgiivf3lxsi')
148const newAgent = new Agent(restoreRes)
149```
150
151If the session needs to be refreshed, `.restore()` will do this for you before returning a session.
152
153## Additional Reading
154
155- [Atproto OAuth Spec](https://atproto.com/specs/oauth)
156- [Atproto Web OAuth Example](https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-browser-example)