atproto_auth 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +17 -2
- data/CHANGELOG.md +23 -2
- data/PROJECT_STRUCTURE.txt +10129 -0
- data/README.md +88 -2
- data/examples/confidential_client/.gitignore +2 -0
- data/examples/confidential_client/Gemfile.lock +6 -0
- data/examples/confidential_client/README.md +86 -9
- data/examples/confidential_client/app.rb +83 -12
- data/examples/confidential_client/{public/client-metadata.json → config/client-metadata.example.json} +5 -4
- data/examples/confidential_client/screenshots/screenshot-1-sign-in.png +0 -0
- data/examples/confidential_client/screenshots/screenshot-2-success.png +0 -0
- data/examples/confidential_client/scripts/generate_keys.rb +0 -0
- data/examples/confidential_client/views/authorized.erb +1 -1
- data/lib/atproto_auth/client.rb +98 -38
- data/lib/atproto_auth/client_metadata.rb +2 -2
- data/lib/atproto_auth/configuration.rb +35 -1
- data/lib/atproto_auth/dpop/key_manager.rb +1 -1
- data/lib/atproto_auth/dpop/nonce_manager.rb +30 -47
- data/lib/atproto_auth/encryption.rb +156 -0
- data/lib/atproto_auth/http_client.rb +2 -2
- data/lib/atproto_auth/identity/document.rb +1 -1
- data/lib/atproto_auth/identity/resolver.rb +1 -1
- data/lib/atproto_auth/serialization/base.rb +189 -0
- data/lib/atproto_auth/serialization/dpop_key.rb +29 -0
- data/lib/atproto_auth/serialization/session.rb +77 -0
- data/lib/atproto_auth/serialization/stored_nonce.rb +37 -0
- data/lib/atproto_auth/serialization/token_set.rb +43 -0
- data/lib/atproto_auth/server_metadata/authorization_server.rb +20 -1
- data/lib/atproto_auth/state/session_manager.rb +67 -20
- data/lib/atproto_auth/storage/interface.rb +112 -0
- data/lib/atproto_auth/storage/key_builder.rb +39 -0
- data/lib/atproto_auth/storage/memory.rb +191 -0
- data/lib/atproto_auth/storage/redis.rb +119 -0
- data/lib/atproto_auth/token/refresh.rb +249 -0
- data/lib/atproto_auth/version.rb +1 -1
- data/lib/atproto_auth.rb +29 -1
- metadata +32 -4
- data/examples/confidential_client/config/client-metadata.json +0 -25
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[](https://github.com/testdouble/standard)
|
5
5
|
[](https://www.rubydoc.info/gems/atproto_auth)
|
6
6
|
|
7
|
-
A Ruby implementation of the AT Protocol OAuth specification. This library provides comprehensive support for both client and server-side implementations, with built-in security features including DPoP (Demonstrating Proof of Possession), PAR (Pushed Authorization Requests), and dynamic client registration.
|
7
|
+
A Ruby implementation of the [AT Protocol OAuth specification](https://docs.bsky.app/docs/advanced-guides/oauth-client). This library provides comprehensive support for both client and server-side implementations, with built-in security features including DPoP (Demonstrating Proof of Possession), PAR (Pushed Authorization Requests), and dynamic client registration.
|
8
8
|
|
9
9
|
## Features
|
10
10
|
|
@@ -15,6 +15,8 @@ A Ruby implementation of the AT Protocol OAuth specification. This library provi
|
|
15
15
|
- Comprehensive identity resolution and verification
|
16
16
|
- Automatic token refresh and session management
|
17
17
|
- Robust error handling and recovery mechanisms
|
18
|
+
- Configurable storage backends with built-in Redis support
|
19
|
+
- Encrypted storage of sensitive data
|
18
20
|
|
19
21
|
## Installation
|
20
22
|
|
@@ -41,6 +43,7 @@ gem install atproto_auth
|
|
41
43
|
- Ruby 3.0 or higher
|
42
44
|
- OpenSSL support
|
43
45
|
- For confidential clients: HTTPS-capable domain for client metadata hosting
|
46
|
+
- Optional: Redis 5.0+ for production storage backend
|
44
47
|
|
45
48
|
## Basic Usage
|
46
49
|
|
@@ -59,6 +62,88 @@ AtprotoAuth.configure do |config|
|
|
59
62
|
# Set token lifetimes
|
60
63
|
config.default_token_lifetime = 300 # 5 minutes
|
61
64
|
config.dpop_nonce_lifetime = 300 # 5 minutes
|
65
|
+
|
66
|
+
# Configure storage backend (default is in-memory)
|
67
|
+
config.storage = AtprotoAuth::Storage::Memory.new
|
68
|
+
end
|
69
|
+
|
70
|
+
# For production environments, use Redis storage:
|
71
|
+
AtprotoAuth.configure do |config|
|
72
|
+
# Configure Redis storage
|
73
|
+
config.storage = AtprotoAuth::Storage::Redis.new(
|
74
|
+
redis_client: Redis.new(url: ENV['REDIS_URL'])
|
75
|
+
)
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
### Storage Backends
|
80
|
+
|
81
|
+
The library supports multiple storage backends for managing OAuth state:
|
82
|
+
|
83
|
+
#### In-Memory Storage (Default)
|
84
|
+
```ruby
|
85
|
+
# Default configuration - good for development
|
86
|
+
AtprotoAuth.configure do |config|
|
87
|
+
config.storage = AtprotoAuth::Storage::Memory.new
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
#### Redis Storage (Recommended for Production)
|
92
|
+
```ruby
|
93
|
+
# Redis configuration - recommended for production
|
94
|
+
require 'redis'
|
95
|
+
|
96
|
+
AtprotoAuth.configure do |config|
|
97
|
+
redis_client = Redis.new(
|
98
|
+
url: ENV['REDIS_URL'],
|
99
|
+
ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }
|
100
|
+
)
|
101
|
+
|
102
|
+
config.storage = AtprotoAuth::Storage::Redis.new(
|
103
|
+
redis_client: redis_client
|
104
|
+
)
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
#### Custom Storage Implementation
|
109
|
+
```ruby
|
110
|
+
# Implement your own storage backend
|
111
|
+
class CustomStorage < AtprotoAuth::Storage::Interface
|
112
|
+
def set(key, value, ttl: nil)
|
113
|
+
# Implementation
|
114
|
+
end
|
115
|
+
|
116
|
+
def get(key)
|
117
|
+
# Implementation
|
118
|
+
end
|
119
|
+
|
120
|
+
def delete(key)
|
121
|
+
# Implementation
|
122
|
+
end
|
123
|
+
|
124
|
+
def exists?(key)
|
125
|
+
# Implementation
|
126
|
+
end
|
127
|
+
|
128
|
+
def multi_get(keys)
|
129
|
+
# Implementation
|
130
|
+
end
|
131
|
+
|
132
|
+
def multi_set(hash, ttl: nil)
|
133
|
+
# Implementation
|
134
|
+
end
|
135
|
+
|
136
|
+
def acquire_lock(key, ttl:)
|
137
|
+
# Implementation
|
138
|
+
end
|
139
|
+
|
140
|
+
def release_lock(key)
|
141
|
+
# Implementation
|
142
|
+
end
|
143
|
+
|
144
|
+
def with_lock(key, ttl: 30)
|
145
|
+
# Implementation
|
146
|
+
end
|
62
147
|
end
|
63
148
|
```
|
64
149
|
|
@@ -162,7 +247,8 @@ Built-in security best practices:
|
|
162
247
|
- Constant-time token comparisons
|
163
248
|
- Thread-safe state management
|
164
249
|
- Protection against SSRF attacks
|
165
|
-
- Secure token storage
|
250
|
+
- Secure encrypted token storage
|
251
|
+
- Atomic storage operations with locking
|
166
252
|
|
167
253
|
## Development
|
168
254
|
|
@@ -4,12 +4,14 @@ PATH
|
|
4
4
|
atproto_auth (0.1.0)
|
5
5
|
jose (~> 1.2)
|
6
6
|
jwt (~> 2.9)
|
7
|
+
redis (~> 5.3)
|
7
8
|
|
8
9
|
GEM
|
9
10
|
remote: https://rubygems.org/
|
10
11
|
specs:
|
11
12
|
base64 (0.2.0)
|
12
13
|
concurrent-ruby (1.3.4)
|
14
|
+
connection_pool (2.4.1)
|
13
15
|
dotenv (3.1.4)
|
14
16
|
faraday (2.12.1)
|
15
17
|
faraday-net_http (>= 2.0, < 3.5)
|
@@ -45,6 +47,10 @@ GEM
|
|
45
47
|
rackup (2.2.1)
|
46
48
|
rack (>= 3)
|
47
49
|
rbtree (0.4.6)
|
50
|
+
redis (5.3.0)
|
51
|
+
redis-client (>= 0.22.0)
|
52
|
+
redis-client (0.22.2)
|
53
|
+
connection_pool
|
48
54
|
ruby2_keywords (0.0.5)
|
49
55
|
set (1.1.1)
|
50
56
|
sinatra (4.1.1)
|
@@ -2,14 +2,19 @@
|
|
2
2
|
|
3
3
|
This is an example implementation of a confidential OAuth client for the AT Protocol using the AtprotoAuth gem. It demonstrates how to implement the OAuth flow for a web application, including DPoP token binding and secure session management.
|
4
4
|
|
5
|
+
<img src="https://github.com/jhuckabee/atproto_auth/blob/main/examples/confidential_client/screenshots/screenshot-1-sign-in.png?raw=true" alt="Sign In Form Screenshot" title="Sign In Form" width="500">
|
6
|
+
|
7
|
+
<img src="https://github.com/jhuckabee/atproto_auth/blob/main/examples/confidential_client/screenshots/screenshot-2-success.png?raw=true" alt="Sign In Success Screenshot" title="Sign In Success" width="500">
|
8
|
+
|
5
9
|
## Overview
|
6
10
|
|
7
11
|
The example implements a simple web application using Sinatra that:
|
8
12
|
- Allows users to sign in with their AT Protocol handle (@handle)
|
9
13
|
- Implements the complete OAuth authorization flow
|
10
14
|
- Uses DPoP-bound tokens for API requests
|
11
|
-
- Demonstrates secure session management
|
15
|
+
- Demonstrates secure session management with encryption
|
12
16
|
- Shows how to make authenticated API calls to Bluesky
|
17
|
+
- Provides examples of both development and production storage configurations
|
13
18
|
|
14
19
|
## Requirements
|
15
20
|
|
@@ -17,6 +22,7 @@ The example implements a simple web application using Sinatra that:
|
|
17
22
|
- Bundler
|
18
23
|
- A domain name for your application that matches your client metadata
|
19
24
|
- SSL certificate for your domain (required for production)
|
25
|
+
- Redis (optional, recommended for production)
|
20
26
|
|
21
27
|
## Setup
|
22
28
|
|
@@ -35,19 +41,61 @@ bundle install
|
|
35
41
|
bundle exec ruby scripts/generate_keys.rb > config/keys.json
|
36
42
|
```
|
37
43
|
|
38
|
-
4. Configure your client metadata
|
44
|
+
4. Configure your client metadata:
|
45
|
+
- Copy the example metadata file over:
|
46
|
+
```
|
47
|
+
cp config/client-metadata.example.json config/client-metadata.json
|
48
|
+
```
|
39
49
|
- Set the correct `client_id` URL where your metadata will be hosted
|
40
50
|
- Configure valid `redirect_uris` for your application
|
41
51
|
- Add your generated keys from step 3 to the `jwks` field
|
42
52
|
|
43
53
|
5. Set up environment variables:
|
44
54
|
```bash
|
45
|
-
|
46
|
-
export
|
55
|
+
# Required for session encryption
|
56
|
+
export SESSION_SECRET=your-secure-session-secret
|
57
|
+
|
58
|
+
# Your application's domain name
|
59
|
+
export PERMITTED_DOMAIN=your.domain.com
|
60
|
+
|
61
|
+
# Optional: Redis URL for production storage
|
62
|
+
export REDIS_URL=redis://localhost:6379
|
47
63
|
```
|
48
64
|
|
49
65
|
## Configuration
|
50
66
|
|
67
|
+
### Storage Configuration
|
68
|
+
|
69
|
+
The example app supports both in-memory and Redis storage backends:
|
70
|
+
|
71
|
+
#### Development (In-Memory Storage)
|
72
|
+
```ruby
|
73
|
+
# config/development.rb
|
74
|
+
AtprotoAuth.configure do |config|
|
75
|
+
config.storage = AtprotoAuth::Storage::Memory.new
|
76
|
+
config.logger = Logger.new($stdout)
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
#### Production (Redis Storage)
|
81
|
+
```ruby
|
82
|
+
# config/production.rb
|
83
|
+
require 'redis'
|
84
|
+
|
85
|
+
AtprotoAuth.configure do |config|
|
86
|
+
redis_client = Redis.new(
|
87
|
+
url: ENV.fetch('REDIS_URL'),
|
88
|
+
ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }
|
89
|
+
)
|
90
|
+
|
91
|
+
config.storage = AtprotoAuth::Storage::Redis.new(
|
92
|
+
redis_client: redis_client
|
93
|
+
)
|
94
|
+
|
95
|
+
config.logger = Logger.new($stdout)
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
51
99
|
### Host Authorization
|
52
100
|
|
53
101
|
This application requires specific domain configuration to function properly:
|
@@ -69,7 +117,7 @@ This application requires specific domain configuration to function properly:
|
|
69
117
|
```bash
|
70
118
|
export PERMITTED_DOMAIN=machinename.xyz.ts.net
|
71
119
|
```
|
72
|
-
6. Run the application (see below)
|
120
|
+
6. Run the application (see below)
|
73
121
|
|
74
122
|
Your application will now be accessible via your Tailscale domain with HTTPS enabled.
|
75
123
|
|
@@ -77,20 +125,25 @@ Your application will now be accessible via your Tailscale domain with HTTPS ena
|
|
77
125
|
|
78
126
|
The application uses encrypted sessions to store authorization data. Configure the session secret with:
|
79
127
|
|
80
|
-
```
|
128
|
+
```bash
|
81
129
|
export SESSION_SECRET=your-secure-random-string
|
82
130
|
```
|
83
131
|
|
84
|
-
If not set, a random secret will be generated on startup.
|
132
|
+
If not set, a random secret will be generated on startup (not recommended for production).
|
85
133
|
|
86
134
|
## Running the Application
|
87
135
|
|
136
|
+
### Development
|
88
137
|
```bash
|
89
|
-
bundle exec rackup
|
138
|
+
RACK_ENV=development bundle exec rackup
|
90
139
|
```
|
91
140
|
|
92
|
-
|
141
|
+
### Production
|
142
|
+
```bash
|
143
|
+
RACK_ENV=production bundle exec rackup -E production
|
144
|
+
```
|
93
145
|
|
146
|
+
This will start the server on `http://localhost:9292`.
|
94
147
|
|
95
148
|
## Troubleshooting
|
96
149
|
|
@@ -108,3 +161,27 @@ This will start the server on `http://localhost:9292`.
|
|
108
161
|
- Verify your JWKS configuration
|
109
162
|
- Check that your DPoP proofs are being generated correctly
|
110
163
|
- Ensure your client authentication is working
|
164
|
+
|
165
|
+
4. "Storage errors":
|
166
|
+
- For Redis storage, verify Redis connection settings
|
167
|
+
- Check Redis SSL configuration if using encrypted connections
|
168
|
+
- Ensure proper Redis authentication credentials if required
|
169
|
+
|
170
|
+
5. "Session state lost":
|
171
|
+
- Verify storage configuration is correct
|
172
|
+
- Check Redis connection stability if using Redis storage
|
173
|
+
- Ensure session TTLs are appropriately configured
|
174
|
+
|
175
|
+
## Understanding the Code
|
176
|
+
|
177
|
+
The example demonstrates several important concepts:
|
178
|
+
|
179
|
+
1. **Secure Storage**: The app shows proper configuration of both development (in-memory) and production (Redis) storage backends.
|
180
|
+
|
181
|
+
2. **Token Management**: All tokens are stored securely with encryption in the configured storage backend.
|
182
|
+
|
183
|
+
3. **Session Handling**: The app demonstrates proper session state management with atomic operations and locking.
|
184
|
+
|
185
|
+
4. **Error Recovery**: Includes examples of handling storage failures and token refresh scenarios.
|
186
|
+
|
187
|
+
The code is thoroughly commented to explain these concepts and their implementation details.
|
@@ -9,17 +9,18 @@ require "dotenv/load"
|
|
9
9
|
|
10
10
|
# Main app entry point
|
11
11
|
class ExampleApp < Sinatra::Base
|
12
|
+
def check_stored_session(session_id)
|
13
|
+
return false unless session_id
|
14
|
+
|
15
|
+
settings.oauth_client.authorized?(session_id)
|
16
|
+
rescue AtprotoAuth::SessionError
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
12
20
|
configure :development do
|
13
21
|
register Sinatra::Reloader
|
14
22
|
end
|
15
23
|
|
16
|
-
set :host_authorization, {
|
17
|
-
permitted_hosts: ["localhost", ENV.fetch("PERMITTED_DOMAIN", nil)].compact
|
18
|
-
}
|
19
|
-
|
20
|
-
enable :sessions
|
21
|
-
set :session_secret, ENV.fetch("SESSION_SECRET") { SecureRandom.hex(32) }
|
22
|
-
|
23
24
|
# Initialize the AT Protocol OAuth client
|
24
25
|
configure do
|
25
26
|
# Configure AtprotoAuth settings
|
@@ -30,6 +31,9 @@ class ExampleApp < Sinatra::Base
|
|
30
31
|
)
|
31
32
|
config.default_token_lifetime = 300
|
32
33
|
config.dpop_nonce_lifetime = 300
|
34
|
+
|
35
|
+
# Optionally, use Redis storage instead of in-memory
|
36
|
+
# config.storage = AtprotoAuth::Storage::Redis.new
|
33
37
|
end
|
34
38
|
|
35
39
|
# Load client metadata
|
@@ -45,10 +49,61 @@ class ExampleApp < Sinatra::Base
|
|
45
49
|
)
|
46
50
|
end
|
47
51
|
|
52
|
+
set :host_authorization, {
|
53
|
+
permitted_hosts: ["localhost", ENV.fetch("PERMITTED_DOMAIN", nil)].compact
|
54
|
+
}
|
55
|
+
|
56
|
+
use Rack::Session::Cookie,
|
57
|
+
key: "atproto.session",
|
58
|
+
expire_after: 86_400, # 1 day in seconds
|
59
|
+
secret: ENV.fetch("SESSION_SECRET") { SecureRandom.hex(32) },
|
60
|
+
secure: true, # Only send over HTTPS
|
61
|
+
httponly: true, # Not accessible via JavaScript
|
62
|
+
same_site: :lax # CSRF protection
|
63
|
+
|
64
|
+
helpers do
|
65
|
+
def recover_session
|
66
|
+
session_id = session[:oauth_session_id]
|
67
|
+
return nil unless session_id
|
68
|
+
|
69
|
+
begin
|
70
|
+
# Check if session is still valid
|
71
|
+
return nil unless settings.oauth_client.authorized?(session_id)
|
72
|
+
|
73
|
+
session_id
|
74
|
+
rescue AtprotoAuth::Client::SessionError
|
75
|
+
# Clear invalid session
|
76
|
+
session.delete(:oauth_session_id)
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
48
82
|
get "/" do
|
83
|
+
# Check for existing session
|
84
|
+
redirect "/authorized" if recover_session
|
85
|
+
|
49
86
|
erb :index
|
50
87
|
end
|
51
88
|
|
89
|
+
get "/client-metadata.json" do
|
90
|
+
content_type :json
|
91
|
+
|
92
|
+
# Read metadata from config file
|
93
|
+
metadata_path = File.join(__dir__, "config", "client-metadata.json")
|
94
|
+
metadata = JSON.parse(File.read(metadata_path))
|
95
|
+
|
96
|
+
# Strip private key 'd' component from each key in the JWKS
|
97
|
+
if metadata["jwks"] && metadata["jwks"]["keys"]
|
98
|
+
metadata["jwks"]["keys"] = metadata["jwks"]["keys"].map do |key|
|
99
|
+
key.except("d")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Return sanitized metadata
|
104
|
+
JSON.generate(metadata)
|
105
|
+
end
|
106
|
+
|
52
107
|
# Start OAuth flow
|
53
108
|
post "/auth" do
|
54
109
|
handle = params[:handle]
|
@@ -60,7 +115,7 @@ class ExampleApp < Sinatra::Base
|
|
60
115
|
scope: "atproto"
|
61
116
|
)
|
62
117
|
|
63
|
-
# Store session ID
|
118
|
+
# Store session ID in user's browser session
|
64
119
|
session[:oauth_session_id] = auth[:session_id]
|
65
120
|
|
66
121
|
# Redirect to authorization URL
|
@@ -80,8 +135,8 @@ class ExampleApp < Sinatra::Base
|
|
80
135
|
iss: params[:iss]
|
81
136
|
)
|
82
137
|
|
83
|
-
# Store tokens
|
84
|
-
session[:
|
138
|
+
# Store tokens
|
139
|
+
session[:oauth_session_id] = result[:session_id]
|
85
140
|
|
86
141
|
redirect "/authorized"
|
87
142
|
rescue StandardError => e
|
@@ -91,9 +146,19 @@ class ExampleApp < Sinatra::Base
|
|
91
146
|
|
92
147
|
# Show authorized state and test API call
|
93
148
|
get "/authorized" do
|
94
|
-
|
149
|
+
session_id = session[:oauth_session_id]
|
150
|
+
return redirect "/" unless check_stored_session(session_id)
|
95
151
|
|
96
152
|
begin
|
153
|
+
# Get current session tokens
|
154
|
+
oauth_session = settings.oauth_client.get_tokens(session_id)
|
155
|
+
|
156
|
+
# Check if token needs refresh
|
157
|
+
if oauth_session[:expires_in] < 30
|
158
|
+
# Refresh token
|
159
|
+
oauth_session = settings.oauth_client.refresh_token(session_id)
|
160
|
+
end
|
161
|
+
|
97
162
|
# Make test API call to com.atproto.identity.resolveHandle
|
98
163
|
conn = Faraday.new(url: "https://api.bsky.app") do |f|
|
99
164
|
f.request :json
|
@@ -102,7 +167,7 @@ class ExampleApp < Sinatra::Base
|
|
102
167
|
|
103
168
|
# Get auth headers for request
|
104
169
|
headers = settings.oauth_client.auth_headers(
|
105
|
-
session_id:
|
170
|
+
session_id: session_id,
|
106
171
|
method: "GET",
|
107
172
|
url: "https://api.bsky.app/xrpc/com.atproto.identity.resolveHandle"
|
108
173
|
)
|
@@ -114,6 +179,7 @@ class ExampleApp < Sinatra::Base
|
|
114
179
|
end
|
115
180
|
|
116
181
|
@api_result = response.body
|
182
|
+
@session = oauth_session
|
117
183
|
erb :authorized
|
118
184
|
rescue StandardError => e
|
119
185
|
session[:error] = "API call failed: #{e.message}"
|
@@ -122,6 +188,11 @@ class ExampleApp < Sinatra::Base
|
|
122
188
|
end
|
123
189
|
|
124
190
|
get "/signout" do
|
191
|
+
if session[:oauth_session_id]
|
192
|
+
# Clean up stored session
|
193
|
+
settings.oauth_client.remove_session(session[:oauth_session_id])
|
194
|
+
end
|
195
|
+
|
125
196
|
session.clear
|
126
197
|
session[:notice] = "Successfully signed out"
|
127
198
|
redirect "/"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
|
-
"client_id": "https://
|
2
|
+
"client_id": "https://YOUR_DOMAIN_HERE/client-metadata.json",
|
3
3
|
"client_name": "AT Protocol OAuth Ruby Example",
|
4
|
-
"redirect_uris": ["https://
|
4
|
+
"redirect_uris": ["https://YOUR_DOMAIN_HERE/callback"],
|
5
5
|
"grant_types": ["authorization_code", "refresh_token"],
|
6
6
|
"response_types": ["code"],
|
7
7
|
"scope": "atproto",
|
@@ -14,10 +14,11 @@
|
|
14
14
|
{
|
15
15
|
"use": "sig",
|
16
16
|
"kid": "key-1",
|
17
|
-
"x": "
|
17
|
+
"x": "...",
|
18
18
|
"crv": "P-256",
|
19
|
+
"d": "...",
|
19
20
|
"kty": "EC",
|
20
|
-
"y": "
|
21
|
+
"y": "..."
|
21
22
|
}
|
22
23
|
]
|
23
24
|
}
|
Binary file
|
Binary file
|
File without changes
|
@@ -13,7 +13,7 @@
|
|
13
13
|
|
14
14
|
<div class="token-info">
|
15
15
|
<p class="mb-1 text-gray-600 dark:text-gray-400 text-sm font-medium">Token Information</p>
|
16
|
-
<pre class="rounded-lg text-gray-700 dark:text-gray-100 bg-gray-100 dark:bg-slate-800 border border-gray-400 p-2 overflow-auto"><code><%= JSON.pretty_generate(session
|
16
|
+
<pre class="rounded-lg text-gray-700 dark:text-gray-100 bg-gray-100 dark:bg-slate-800 border border-gray-400 p-2 overflow-auto"><code><%= JSON.pretty_generate(@session) %></code></pre>
|
17
17
|
</div>
|
18
18
|
|
19
19
|
<div class="api-test">
|