-
-
Notifications
You must be signed in to change notification settings - Fork 14
Description
Signing with DPoP is required for AT Protocol:
DPoP is mandatory for all clients, so this must be present and true
— https://atproto.com/specs/oauth#demonstrating-proof-of-possession-d-po-p
The current design of OAuthenticator makes it the consumer's responsibility to correctly manage DPoP which is a pretty complicated task. For example, DPoP nonces could be different depending on which server you're talking to, e.g., if the resource server and authorization server are different, then you may have multiple DPoP Nonces, one for each. Figuring out what parameters the DPoP JWT requires is also somewhat non-trivial.
It would require bringing in more dependencies, but would simplify the interface for consumers. (e.g., JWT generation dependencies)
The Login struct should also persist the DPoP Private Key, and we'd likely want to provide a simple key/value storage for storing DPoP Nonces ( host -> nonce ), such that when making an authenticated request we can retrieve the correct nonce for the request host.
So we would add:
- add
dpopKeytoLogin, which can benil - a
DPoPStorageinterface & input toConfigurationwhich is a simple key/value store, where the key is the host, and the value is the nonce for that host.
Currently the logic for defining the DPoP JWTs looks like the following:
public static func makeDpopSigner(p256Key: P256.Signing.PrivateKey) throws
-> DPoPSigner.JWTGenerator
{
{ (parameters: DPoPSigner.JWTParameters) async throws -> String in
let payload: any Encodable = {
if let nonce = parameters.nonce,
let authorizationServerIssuer = parameters
.issuingServer,
let accessTokenHash = parameters.tokenHash
{
DPoPRequestPayload(
httpMethod: parameters.httpMethod,
httpRequestURL: parameters.requestEndpoint,
createdAt: Int(
Date.now.timeIntervalSince1970),
expiresAt: Int(
Date.now.timeIntervalSince1970
+ 3600),
nonce: nonce,
authorizationServerIssuer:
authorizationServerIssuer,
accessTokenHash: accessTokenHash
)
} else {
DPoPTokenPayload(
httpMethod: parameters.httpMethod,
httpRequestURL: parameters.requestEndpoint,
createdAt: Int(
Date.now.timeIntervalSince1970),
expiresAt: Int(
Date.now.timeIntervalSince1970
+ 3600),
nonce: parameters.nonce
)
}
}()
return try await JWTSerializerLite.sign(
payload,
with: JWTLexiconLite.JWTHeader(
typ: parameters.keyType,
jwk: JWTLexiconLite.JWK(key: p256Key)
),
using: ECDSASigner(key: p256Key)
)
}
}
}However, this is always going to be the same for all AT Protocol client implementations. Therefore it should probably be internal to the AT Protocol implementation in OAuthenticator