authproto 0.3.0.beta1
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 +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +31 -0
- data/.tm_properties +1 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +52 -0
- data/MIT-LICENSE.txt +21 -0
- data/README.md +304 -0
- data/Rakefile +8 -0
- data/SECURITY.md +16 -0
- data/examples/confidential_client/.gitignore +5 -0
- data/examples/confidential_client/Gemfile +14 -0
- data/examples/confidential_client/Gemfile.lock +88 -0
- data/examples/confidential_client/README.md +226 -0
- data/examples/confidential_client/app.rb +290 -0
- data/examples/confidential_client/config/client-metadata.example.json +25 -0
- data/examples/confidential_client/config.ru +4 -0
- data/examples/confidential_client/public/styles.css +70 -0
- 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 +15 -0
- data/examples/confidential_client/views/authorized.erb +46 -0
- data/examples/confidential_client/views/index.erb +44 -0
- data/examples/confidential_client/views/layout.erb +11 -0
- data/examples/minisky_console/.gitignore +2 -0
- data/examples/minisky_console/Gemfile +12 -0
- data/examples/minisky_console/Gemfile.lock +131 -0
- data/examples/minisky_console/README.md +112 -0
- data/examples/minisky_console/app.rb +50 -0
- data/examples/minisky_console/bin/console +6 -0
- data/examples/minisky_console/config.ru +5 -0
- data/examples/minisky_console/localhost.rb +53 -0
- data/examples/minisky_console/production.rb +32 -0
- data/lib/authproto/client.rb +583 -0
- data/lib/authproto/client_metadata.rb +478 -0
- data/lib/authproto/configuration.rb +139 -0
- data/lib/authproto/dpop/client.rb +161 -0
- data/lib/authproto/dpop/key_manager.rb +223 -0
- data/lib/authproto/dpop/nonce_manager.rb +145 -0
- data/lib/authproto/dpop/proof_generator.rb +124 -0
- data/lib/authproto/dpop/stored_nonce.rb +37 -0
- data/lib/authproto/encryption.rb +250 -0
- data/lib/authproto/errors.rb +75 -0
- data/lib/authproto/http_client.rb +320 -0
- data/lib/authproto/insecure_http_client.rb +24 -0
- data/lib/authproto/localhost_server.rb +274 -0
- data/lib/authproto/minisky.rb +273 -0
- data/lib/authproto/par/client.rb +240 -0
- data/lib/authproto/par/client_assertion.rb +69 -0
- data/lib/authproto/par/request.rb +243 -0
- data/lib/authproto/par/response.rb +43 -0
- data/lib/authproto/par.rb +48 -0
- data/lib/authproto/pkce.rb +125 -0
- data/lib/authproto/serialization/base.rb +258 -0
- data/lib/authproto/serialization/dpop_key.rb +38 -0
- data/lib/authproto/serialization/session.rb +88 -0
- data/lib/authproto/serialization/stored_nonce.rb +46 -0
- data/lib/authproto/serialization/token_set.rb +54 -0
- data/lib/authproto/server_metadata/authorization_server.rb +267 -0
- data/lib/authproto/server_metadata/origin_url.rb +71 -0
- data/lib/authproto/server_metadata/resource_server.rb +98 -0
- data/lib/authproto/server_metadata.rb +28 -0
- data/lib/authproto/state/session.rb +168 -0
- data/lib/authproto/state/session_manager.rb +162 -0
- data/lib/authproto/state/token_set.rb +101 -0
- data/lib/authproto/state.rb +61 -0
- data/lib/authproto/storage/interface.rb +121 -0
- data/lib/authproto/storage/key_builder.rb +76 -0
- data/lib/authproto/storage/memory.rb +222 -0
- data/lib/authproto/storage/redis.rb +156 -0
- data/lib/authproto/storage/sqlite.rb +281 -0
- data/lib/authproto/token/refresh.rb +414 -0
- data/lib/authproto/version.rb +8 -0
- data/lib/authproto.rb +18 -0
- data/sig/authproto/client.rbs +43 -0
- data/sig/authproto/client_metadata.rbs +113 -0
- data/sig/authproto/dependencies.rbs +19 -0
- data/sig/authproto/dpop/client.rbs +23 -0
- data/sig/authproto/dpop/key_manager.rbs +26 -0
- data/sig/authproto/dpop/nonce_manager.rbs +23 -0
- data/sig/authproto/dpop/proof_generator.rbs +40 -0
- data/sig/authproto/dpop/stored_nonce.rbs +10 -0
- data/sig/authproto/encryption.rbs +42 -0
- data/sig/authproto/http_client.rbs +45 -0
- data/sig/authproto/insecure_http_client.rbs +8 -0
- data/sig/authproto/localhost_server.rbs +30 -0
- data/sig/authproto/minisky_oauth.rbs +22 -0
- data/sig/authproto/par/client.rbs +25 -0
- data/sig/authproto/par/client_assertion.rbs +10 -0
- data/sig/authproto/par/request.rbs +73 -0
- data/sig/authproto/par/response.rbs +17 -0
- data/sig/authproto/par.rbs +7 -0
- data/sig/authproto/pkce.rbs +24 -0
- data/sig/authproto/serialization/base.rbs +36 -0
- data/sig/authproto/serialization/dpop_key.rbs +14 -0
- data/sig/authproto/serialization/session.rbs +19 -0
- data/sig/authproto/serialization/stored_nonce.rbs +14 -0
- data/sig/authproto/serialization/token_set.rbs +14 -0
- data/sig/authproto/server_metadata/authorization_server.rbs +71 -0
- data/sig/authproto/server_metadata/origin_url.rbs +21 -0
- data/sig/authproto/server_metadata/resource_server.rbs +27 -0
- data/sig/authproto/state/session.rbs +28 -0
- data/sig/authproto/state/session_manager.rbs +15 -0
- data/sig/authproto/state/token_set.rbs +40 -0
- data/sig/authproto/state.rbs +6 -0
- data/sig/authproto/storage/interface.rbs +20 -0
- data/sig/authproto/storage/key_builder.rbs +16 -0
- data/sig/authproto/storage/memory.rbs +22 -0
- data/sig/authproto/storage/redis.rbs +19 -0
- data/sig/authproto/storage/redis_client.rbs +11 -0
- data/sig/authproto/storage/sqlite.rbs +26 -0
- data/sig/authproto/token/refresh.rbs +47 -0
- data/sig/authproto/version.rbs +3 -0
- data/sig/authproto.rbs +54 -0
- metadata +200 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 77c4682515836e03bc41e1d03724f5ca2a85e65a11655b2af8a8ba09049a73fc
|
|
4
|
+
data.tar.gz: 0ba9433f0ee89b8617354d1e0245644f421fc1e7264597391b2efa5ebc015f79
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ebbd4f4c9df22f002bbe9be7c02b502bf4fcf5c056d4d0e9290a2c91dde8f03b4756e3b3db9f306cc1b9d5be6be183b88ace24ddb195733c37e92823ec0d5d78
|
|
7
|
+
data.tar.gz: 3b96e49a8ea9bbb555dab5996c0e6a53f0894616928c4416199f00561f2d9603cf327d0c868cf66501fd7b5982a8ec985d9e27e5032de274ef8daaccad58dbb4
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
SuggestExtensions: false
|
|
4
|
+
TargetRubyVersion: 3.2
|
|
5
|
+
|
|
6
|
+
Metrics/BlockLength:
|
|
7
|
+
Enabled: false
|
|
8
|
+
|
|
9
|
+
Metrics/ClassLength:
|
|
10
|
+
Enabled: false
|
|
11
|
+
|
|
12
|
+
Metrics/ModuleLength:
|
|
13
|
+
Enabled: false
|
|
14
|
+
|
|
15
|
+
Metrics/MethodLength:
|
|
16
|
+
Enabled: false
|
|
17
|
+
|
|
18
|
+
Metrics/AbcSize:
|
|
19
|
+
Enabled: false
|
|
20
|
+
|
|
21
|
+
Metrics/PerceivedComplexity:
|
|
22
|
+
Enabled: false
|
|
23
|
+
|
|
24
|
+
Metrics/CyclomaticComplexity:
|
|
25
|
+
Enabled: false
|
|
26
|
+
|
|
27
|
+
Style/StringLiterals:
|
|
28
|
+
EnforcedStyle: double_quotes
|
|
29
|
+
|
|
30
|
+
Style/StringLiteralsInInterpolation:
|
|
31
|
+
EnforcedStyle: double_quotes
|
data/.tm_properties
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
excludeInFolderSearch = "{$excludeInFolderSearch,coverage,doc}"
|
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
## [0.2.5] - 2024-12-13
|
|
2
|
+
|
|
3
|
+
Fixed:
|
|
4
|
+
|
|
5
|
+
- Additional token refresh edge case handling
|
|
6
|
+
|
|
7
|
+
## [0.2.4] - 2024-12-13
|
|
8
|
+
|
|
9
|
+
Fixed:
|
|
10
|
+
|
|
11
|
+
- Token refresh now correctly handles expired tokens
|
|
12
|
+
|
|
13
|
+
## [0.2.2] - 2024-12-12
|
|
14
|
+
|
|
15
|
+
Added:
|
|
16
|
+
|
|
17
|
+
- Added support for did:web users
|
|
18
|
+
|
|
19
|
+
## [0.2.1] - 2024-12-11
|
|
20
|
+
|
|
21
|
+
Fixed:
|
|
22
|
+
|
|
23
|
+
- Fixed a bug with access token hash generation
|
|
24
|
+
|
|
25
|
+
## [0.2.0] - 2024-12-10
|
|
26
|
+
|
|
27
|
+
Added:
|
|
28
|
+
|
|
29
|
+
- Client#handle_callback now returns session did as part of the response
|
|
30
|
+
|
|
31
|
+
## [0.1.0] - 2024-12-07
|
|
32
|
+
|
|
33
|
+
Added:
|
|
34
|
+
|
|
35
|
+
- Configurable storage backend system for managing OAuth state
|
|
36
|
+
- In-memory storage implementation included by default
|
|
37
|
+
- Redis storage implementation for production environments
|
|
38
|
+
- Thread-safe storage operations with atomic locks
|
|
39
|
+
- Storage encryption for sensitive data
|
|
40
|
+
- Automatic cleanup of expired tokens and session data
|
|
41
|
+
- Storage interface for custom backend implementations
|
|
42
|
+
|
|
43
|
+
Changed:
|
|
44
|
+
|
|
45
|
+
- Storage configuration is now required in AtprotoAuth.configure
|
|
46
|
+
- Default configuration uses thread-safe in-memory storage
|
|
47
|
+
- Session and token management now use configured storage backend
|
|
48
|
+
- Improved thread safety for all storage operations
|
|
49
|
+
|
|
50
|
+
## [0.0.1] - 2024-12-05
|
|
51
|
+
|
|
52
|
+
- Initial release
|
data/MIT-LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Josh Huckabee, (c) 2026 Jakub Suder
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# AuthProto
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/authproto)
|
|
4
|
+
[](https://www.rubydoc.info/gems/authproto)
|
|
5
|
+
|
|
6
|
+
A Ruby implementation of the [AT Protocol OAuth specification](https://atproto.com/specs/oauth). This library provides support for both client and server-side implementations, with built-in security features including [DPoP](https://datatracker.ietf.org/doc/html/rfc9449), [PAR](https://datatracker.ietf.org/doc/html/rfc9126), and dynamic client registration.
|
|
7
|
+
|
|
8
|
+
> [!WARNING]
|
|
9
|
+
> I have inherited this gem from someone else, and haven't fully reviewed all code yet. Consider this an alpha version.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- Complete AT Protocol OAuth 2.0 implementation
|
|
15
|
+
- Secure by default with mandatory DPoP and PKCE
|
|
16
|
+
- Support for confidential (backend) and public clients
|
|
17
|
+
- Thread-safe session and token management
|
|
18
|
+
- Comprehensive identity resolution and verification
|
|
19
|
+
- Automatic token refresh and session management
|
|
20
|
+
- Robust error handling and recovery mechanisms
|
|
21
|
+
- Configurable storage backends with built-in SQLite and Redis support
|
|
22
|
+
- Encrypted storage of sensitive data
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Add this line to your application's Gemfile:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
gem 'authproto'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
And then execute:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
bundle install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or install it yourself as:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
gem install authproto
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Requirements
|
|
45
|
+
|
|
46
|
+
- Ruby 3.3 or higher
|
|
47
|
+
- OpenSSL support
|
|
48
|
+
- For confidential clients: HTTPS-capable domain for client metadata hosting
|
|
49
|
+
- Optional: SQLite or Redis storage adapter gems for production use
|
|
50
|
+
|
|
51
|
+
## Basic Usage
|
|
52
|
+
|
|
53
|
+
### Configuration
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
require 'authproto'
|
|
57
|
+
|
|
58
|
+
AuthProto.configure do |config|
|
|
59
|
+
# Configure HTTP client settings
|
|
60
|
+
config.http_client = AuthProto::HTTPClient.new(
|
|
61
|
+
timeout: 10,
|
|
62
|
+
verify_ssl: true
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Set token lifetimes
|
|
66
|
+
config.default_token_lifetime = 300 # 5 minutes
|
|
67
|
+
config.dpop_nonce_lifetime = 300 # 5 minutes
|
|
68
|
+
|
|
69
|
+
# Configure storage backend (default is in-memory)
|
|
70
|
+
config.storage = AuthProto::Storage::Memory.new
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Storage adapter gems are optional dependencies. Add one of these:
|
|
74
|
+
gem 'sqlite3', '~> 2.9'
|
|
75
|
+
gem 'redis', '~> 5.4'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Local HTTP debugging
|
|
79
|
+
|
|
80
|
+
For debugging a flow that needs both local HTTP and remote HTTPS (handle/PLC
|
|
81
|
+
resolution, metadata discovery and OAuth requests), explicitly select:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
AuthProto.configure do |config|
|
|
85
|
+
config.http_client = AuthProto::InsecureHTTPClient.new(timeout: 10)
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`InsecureHTTPClient` accepts HTTP and HTTPS, including loopback and private-network
|
|
90
|
+
destinations, and permits redirects between them. It disables the regular client's
|
|
91
|
+
scheme/IP restrictions for development; HTTPS certificate verification stays enabled
|
|
92
|
+
by default. Timeouts, response size limits and request headers are inherited.
|
|
93
|
+
|
|
94
|
+
The regular `HTTPClient` accepts HTTPS and rejects loopback/private addresses.
|
|
95
|
+
Use it for remote OAuth servers, including when your application's callback is
|
|
96
|
+
local: the browser, rather than this HTTP client, follows the callback redirect.
|
|
97
|
+
|
|
98
|
+
Client metadata fetches call `get(url, follow_redirects: false)` and require HTTP
|
|
99
|
+
200 with an `application/json` response. Custom HTTP clients must support this
|
|
100
|
+
keyword and return the initial response without following redirects when it is false.
|
|
101
|
+
|
|
102
|
+
Client metadata must explicitly set `token_endpoint_auth_method` to `none` for
|
|
103
|
+
public clients or `private_key_jwt` for confidential clients. Omitting it defaults
|
|
104
|
+
to `client_secret_basic` in OAuth metadata, which this library does not support.
|
|
105
|
+
|
|
106
|
+
### Storage Backends
|
|
107
|
+
|
|
108
|
+
The library supports multiple storage backends for managing OAuth state:
|
|
109
|
+
|
|
110
|
+
#### In-Memory Storage (Default)
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# Default configuration - good for development
|
|
114
|
+
AuthProto.configure do |config|
|
|
115
|
+
config.storage = AuthProto::Storage::Memory.new
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### SQLite Storage
|
|
120
|
+
|
|
121
|
+
SQLite can be used for persistent storage in development or in production setups with
|
|
122
|
+
low-to-moderate write concurrency:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
AuthProto.configure do |config|
|
|
126
|
+
config.storage = AuthProto::Storage::SQLite.new(
|
|
127
|
+
path: '/var/lib/my_app/authproto.sqlite3'
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Add the adapter gem to your Gemfile:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
gem 'sqlite3', '~> 2.9'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### Redis Storage
|
|
139
|
+
|
|
140
|
+
Use Redis for production deployments with multiple hosts or write-heavy scenarios:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
require 'redis'
|
|
144
|
+
|
|
145
|
+
AuthProto.configure do |config|
|
|
146
|
+
redis_client = Redis.new(
|
|
147
|
+
url: ENV['REDIS_URL'],
|
|
148
|
+
ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
config.storage = AuthProto::Storage::Redis.new(
|
|
152
|
+
redis_client: redis_client
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Add the adapter gem to your Gemfile:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
gem 'redis', '~> 5.4'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### Custom Storage Implementation
|
|
164
|
+
```ruby
|
|
165
|
+
# Implement your own storage backend
|
|
166
|
+
class CustomStorage < AuthProto::Storage::Interface
|
|
167
|
+
def set(key, value, ttl: nil)
|
|
168
|
+
# Implementation
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def get(key)
|
|
172
|
+
# Implementation
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def delete(key)
|
|
176
|
+
# Implementation
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def exists?(key)
|
|
180
|
+
# Implementation
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def with_lock(key, ttl: 30)
|
|
184
|
+
# Implementation
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Confidential Client Example
|
|
190
|
+
|
|
191
|
+
Here's a basic example of using the library in a confidential client application:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
# Initialize client with metadata
|
|
195
|
+
client = AuthProto::Client.new(
|
|
196
|
+
client_id: "https://app.example.com/oauth-client-metadata.json",
|
|
197
|
+
redirect_uri: "https://app.example.com/callback",
|
|
198
|
+
metadata: {
|
|
199
|
+
# Your client metadata...
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Start authorization flow
|
|
204
|
+
auth = client.authorize(
|
|
205
|
+
handle: "user.bsky.social",
|
|
206
|
+
scope: "atproto"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Use auth[:url] to redirect user
|
|
210
|
+
|
|
211
|
+
# Handle callback
|
|
212
|
+
tokens = client.handle_callback(
|
|
213
|
+
code: params[:code],
|
|
214
|
+
state: params[:state],
|
|
215
|
+
iss: params[:iss]
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Make authenticated requests
|
|
219
|
+
headers = client.auth_headers(
|
|
220
|
+
session_id: tokens[:session_id],
|
|
221
|
+
method: "GET",
|
|
222
|
+
url: "https://api.example.com/resource"
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
For a complete working example of a confidential client implementation, check out the example application in `examples/confidential_client/`. This Sinatra-based web application demonstrates:
|
|
227
|
+
- Complete OAuth flow implementation
|
|
228
|
+
- Session management
|
|
229
|
+
- DPoP token binding
|
|
230
|
+
- Making authenticated API requests
|
|
231
|
+
- Proper error handling
|
|
232
|
+
- Key generation and management
|
|
233
|
+
|
|
234
|
+
See `examples/confidential_client/README.md` for setup instructions and implementation details.
|
|
235
|
+
|
|
236
|
+
### Public Client Example
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
client = AuthProto::Client.new(
|
|
240
|
+
client_id: "https://app.example.com/oauth-client-metadata.json",
|
|
241
|
+
redirect_uri: "https://app.example.com/callback"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Browser will open authorization URL
|
|
245
|
+
auth = client.authorize(
|
|
246
|
+
handle: "user.bsky.social",
|
|
247
|
+
scope: "atproto"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# After callback, exchange code for tokens
|
|
251
|
+
tokens = client.handle_callback(
|
|
252
|
+
code: params[:code],
|
|
253
|
+
state: params[:state],
|
|
254
|
+
iss: params[:iss]
|
|
255
|
+
)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Features In Detail
|
|
259
|
+
|
|
260
|
+
### Identity Resolution
|
|
261
|
+
|
|
262
|
+
The library uses DIDKit for AT Protocol identity resolution:
|
|
263
|
+
|
|
264
|
+
- Handle to DID resolution (DNS-based or HTTP fallback)
|
|
265
|
+
- DID document fetching and validation
|
|
266
|
+
- PDS (Personal Data Server) location verification
|
|
267
|
+
- Bidirectional handle verification
|
|
268
|
+
- Authorization server binding verification
|
|
269
|
+
|
|
270
|
+
### Token & Session Management
|
|
271
|
+
|
|
272
|
+
Comprehensive token lifecycle management:
|
|
273
|
+
|
|
274
|
+
- Secure token storage with encryption
|
|
275
|
+
- Automatic token refresh
|
|
276
|
+
- DPoP proof generation and binding
|
|
277
|
+
- Session state tracking
|
|
278
|
+
- Cleanup of expired sessions
|
|
279
|
+
|
|
280
|
+
### Security Features
|
|
281
|
+
|
|
282
|
+
Built-in security best practices:
|
|
283
|
+
|
|
284
|
+
- Mandatory PKCE for all flows
|
|
285
|
+
- DPoP token binding
|
|
286
|
+
- Constant-time token comparisons
|
|
287
|
+
- Thread-safe state management
|
|
288
|
+
- Protection against SSRF attacks
|
|
289
|
+
- Secure encrypted token storage
|
|
290
|
+
- Atomic storage operations with locking
|
|
291
|
+
|
|
292
|
+
## Development
|
|
293
|
+
|
|
294
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` (or `bundle exec rake spec`) to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
295
|
+
|
|
296
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
297
|
+
|
|
298
|
+
## Contributing
|
|
299
|
+
|
|
300
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mackuba/authproto.
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/SECURITY.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## How To Report a Vulnerability
|
|
4
|
+
|
|
5
|
+
If you find a potential vulnerability in one of my projects, it's recommended not to report it through a standard publicly-readable issue report, in order to avoid giving possible clues to any potential attackers who could abuse it before it's fixed (see: ["Just a rumour of a bug is enough to find a security exploit these days"](https://anil.recoil.org/notes/rumour-is-the-exploit)).
|
|
6
|
+
|
|
7
|
+
You can use one of these to contact me instead:
|
|
8
|
+
|
|
9
|
+
- [@mackuba.eu](https://bsky.app/profile/did:plc:oio4hkxaop4ao4wz2pp3f4cr) on Bluesky (my DMs should be open, or mention me discretely with a request for contact)
|
|
10
|
+
- [@mackuba@martianbase.net](https://martianbase.net/@mackuba) on Mastodon (my personal instance)
|
|
11
|
+
- email: mackuba `@` protonmail.ch (optionally with [this PGP key](https://mail-api.proton.me/pks/lookup?op=get&search=mackuba@protonmail.ch))
|
|
12
|
+
- ["Report a vulnerability"](https://github.com/mackuba/didkit/security/advisories/new) form on GitHub
|
|
13
|
+
|
|
14
|
+
I do not check my Twitter/X or LinkedIn accounts regularly.
|
|
15
|
+
|
|
16
|
+
Note: please don't submit AI-reported issues that have not been verified manually :)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "authproto", path: "../.."
|
|
6
|
+
gem "dotenv"
|
|
7
|
+
gem "faraday"
|
|
8
|
+
gem "json"
|
|
9
|
+
gem "puma"
|
|
10
|
+
gem "rackup"
|
|
11
|
+
gem "redis"
|
|
12
|
+
gem "sinatra"
|
|
13
|
+
gem "sinatra-contrib"
|
|
14
|
+
gem "sqlite3", "~> 2.9"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: ../..
|
|
3
|
+
specs:
|
|
4
|
+
authproto (0.2.5)
|
|
5
|
+
jose (~> 1.2)
|
|
6
|
+
jwt (~> 3.3)
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
base64 (0.3.0)
|
|
12
|
+
concurrent-ruby (1.3.8)
|
|
13
|
+
connection_pool (3.0.2)
|
|
14
|
+
dotenv (3.2.0)
|
|
15
|
+
faraday (2.14.3)
|
|
16
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
17
|
+
json
|
|
18
|
+
logger
|
|
19
|
+
faraday-net_http (3.4.4)
|
|
20
|
+
net-http (~> 0.5)
|
|
21
|
+
immutable-ruby (0.2.0)
|
|
22
|
+
concurrent-ruby (~> 1.1)
|
|
23
|
+
sorted_set (~> 1.0)
|
|
24
|
+
jose (1.2.0)
|
|
25
|
+
base64
|
|
26
|
+
immutable-ruby
|
|
27
|
+
json (2.21.1)
|
|
28
|
+
jwt (3.3.0)
|
|
29
|
+
base64
|
|
30
|
+
logger (1.7.0)
|
|
31
|
+
multi_json (1.21.1)
|
|
32
|
+
mustermann (3.1.1)
|
|
33
|
+
net-http (0.9.1)
|
|
34
|
+
uri (>= 0.11.1)
|
|
35
|
+
nio4r (2.7.5)
|
|
36
|
+
puma (8.0.2)
|
|
37
|
+
nio4r (~> 2.0)
|
|
38
|
+
rack (3.2.6)
|
|
39
|
+
rack-protection (4.2.1)
|
|
40
|
+
base64 (>= 0.1.0)
|
|
41
|
+
logger (>= 1.6.0)
|
|
42
|
+
rack (>= 3.0.0, < 4)
|
|
43
|
+
rack-session (2.1.2)
|
|
44
|
+
base64 (>= 0.1.0)
|
|
45
|
+
rack (>= 3.0.0)
|
|
46
|
+
rackup (2.3.1)
|
|
47
|
+
rack (>= 3)
|
|
48
|
+
rbtree (0.4.7)
|
|
49
|
+
redis (5.4.1)
|
|
50
|
+
redis-client (>= 0.22.0)
|
|
51
|
+
redis-client (0.30.1)
|
|
52
|
+
connection_pool
|
|
53
|
+
sinatra (4.2.1)
|
|
54
|
+
logger (>= 1.6.0)
|
|
55
|
+
mustermann (~> 3.0)
|
|
56
|
+
rack (>= 3.0.0, < 4)
|
|
57
|
+
rack-protection (= 4.2.1)
|
|
58
|
+
rack-session (>= 2.0.0, < 3)
|
|
59
|
+
tilt (~> 2.0)
|
|
60
|
+
sinatra-contrib (4.2.1)
|
|
61
|
+
multi_json (>= 0.0.2)
|
|
62
|
+
mustermann (~> 3.0)
|
|
63
|
+
rack-protection (= 4.2.1)
|
|
64
|
+
sinatra (= 4.2.1)
|
|
65
|
+
tilt (~> 2.0)
|
|
66
|
+
sorted_set (1.1.0)
|
|
67
|
+
rbtree
|
|
68
|
+
sqlite3 (2.9.5-arm64-darwin)
|
|
69
|
+
tilt (2.8.0)
|
|
70
|
+
uri (1.1.1)
|
|
71
|
+
|
|
72
|
+
PLATFORMS
|
|
73
|
+
arm64-darwin-24
|
|
74
|
+
|
|
75
|
+
DEPENDENCIES
|
|
76
|
+
authproto!
|
|
77
|
+
dotenv
|
|
78
|
+
faraday
|
|
79
|
+
json
|
|
80
|
+
puma
|
|
81
|
+
rackup
|
|
82
|
+
redis
|
|
83
|
+
sinatra
|
|
84
|
+
sinatra-contrib
|
|
85
|
+
sqlite3 (~> 2.9)
|
|
86
|
+
|
|
87
|
+
BUNDLED WITH
|
|
88
|
+
4.0.17
|