Pubky Auth
Pubky Auth is a protocol for using user's root key to authenticate themselves to a 3rd party app and to authorize that app to access resources on the user's Homeserver.
Glossary
- Authenticator: An application holding the Keypair used in authentication. Eg Pubky Ring.
- Pubky: The public key (pubky) identifying the user.
- Homeserver: The public key (pubky) identifying the receiver of the authentication request, usually a server.
- 3rd Party App: An application trying to get authorized to access some resources belonging to the Pubky.
- Capabilities: A list of strings specifying scopes and the actions that can be performed on them.
- HTTP relay: An independent HTTP relay (or the backend of the 3rd Party App) forwarding the
AuthTokento the frontend.
Flow
Here's how is works:
sequenceDiagram
participant User
participant Authenticator
participant 3rd Party App
participant HTTP Relay
participant Homeserver
autonumber
3rd Party App -->>3rd Party App : Generate a unique secret
3rd Party App ->>+HTTP Relay: Subscribe
note over 3rd Party App ,HTTP Relay: channel Id = hash(client secret)
3rd Party App ->>Authenticator: Show QR code
note over 3rd Party App ,Authenticator: required Capabilities,<br/>relay url, and client secret
Authenticator-->>User: Display consent form
User -->>Authenticator: Confirm consent
Authenticator-->>Authenticator: Sign AuthToken & encrypt with client secret
Authenticator->>HTTP Relay: Send encrypted AuthToken
note over Authenticator ,HTTP Relay: channel Id = hash(client secret)
HTTP Relay->>3rd Party App : Forward Encrypted AuthToken
HTTP Relay->>-Authenticator: Ok
3rd Party App -->>3rd Party App : Decrypt AuthToken & Resolve user's homeserver
3rd Party App ->>+Homeserver: Send AuthToken
Homeserver-->>Homeserver: Verify AuthToken
Homeserver->>-3rd Party App : Return SessionId
3rd Party App ->>+Homeserver: Request resources
Homeserver-->>Homeserver: Check Session capabilities
Homeserver ->>-3rd Party App: Ok
3rd Party Appgenerates a unique (32 bytes)client_secret.3rd Party Appuses thebase64url(hash(client_secret))as achannel_idand subscribe to that channel on theHTTP Relayit is using.3rd Party Appformats a Pubky Auth url:
pubkyauth:///
?relay=<HTTP Relay base (without channel_id)>
&caps=<required capabilities>
&secret=<base64url(client_secret)>
for example
pubkyauth:///
?relay=https://demo.httprelay.io/link
&caps=/pub/pubky.app/:rw,/pub/example.com/nested:rw
&secret=mAa8kGmlrynGzQLteDVW6-WeUGnfvHTpEmbNerbWfPI
and finally show that URL as a QR code to the user.
- The
Authenticatorapp scans that QR code, parses the URL and shows a consent form for the user. - The user decides whether or not to grant these capabilities to the
3rd Party App. - If the user approves, the
Authenticatoruses their Keypair to sign anAuthToken, then encrypts that token with theclient_secret. Thechannel_idis then calculated by hashing that secret and the encrypted token is sent to the callback url, which is therelay+channel_id. HTTP Relayforwards the encryptedAuthTokento the3rd Party Appfrontend and confirms the delivery with theAuthenticator.3rd Party Appdecrypts the AuthToken using itsclient_secret, reads thepubkyin it and sends it to theirHomeserverto obtain a session.Homeserververifies the session and stores the correspondingcapabilities.Homeserverreturns a session Id to the frontend to use in subsequent requests.3rd Party Appuses the session Id to access some resource at the Homeserver.Homeserverchecks the session capabilities to see if it is allowed to access that resource.Homeserverresponds to the3rd Party Appwith the resource.
AuthToken encoding
AuthToken = signature namespace version timestamp pubky capabilities
signature = 64OCTET ; ed25519 signature over the rest of the token
namespace = %x50.55.42.4b.59.3a.41.55.54.48 ; "PUBKY:AUTH" in UTF-8 (10 bytes)
version = 1*OCTET ; Version of the AuthToken for future proofing
timestamp = 8OCTET ; Big-endian UNIX timestamp in microseconds
pubky = 32OCTET ; ed25519 public key of the user
capabilities = *(capability *( "," capability ))
capability = scope ":" actions ; Formatted as `/path/to/resource:rw`
scope = absolute-path ; Absolute path, see RFC 3986
absolute-path = 1*( "/" segment )
segment = <segment, see [URI], Section 3.3>
actions = 1*action
action = "r" / "w" ; Read or write (more actions can be specified later)
AuthToken verification
To verify a token the Homeserver should:
- Check the 75th byte (version) and make sure it is
0for this spec. - Deserialize the token
- Verify that the
timestampis within a window from the local time: the default should be 45 seconds in the past and 45 seconds in the future to handle latency and drifts. - Verify that the
pubkyis the signer of thesignatureover the rest of the serialized token after the signature (serialized_token[65..]). - To avoid reuse of the token the
Homeservershould consider thetimestampandpubky(serialized_token[75..115]) as a unique sortable ID, and store it in a sortable key value store, rejecting any token that has the same ID, and removing all IDs that start with a timestamp that is older than the window mentioned in step 3.
Unhosted Apps and Relays
Callback URLs work fine for full-stack applications. There are however many cases where you would want to develop an application without a backend, yet you still would like to let users sign in and bring their own backend. The idea of Unhosted applications is one of the main reasons we are developing Homeservers.
These applications will still need some relay to receive the AuthToken when the Authenticator sends it to the callback URL. Since the AuthToken is a bearer token these relays can intercept it and use it before the unhosted app. That is why we need to encrypt the AuthToken with a key that the relay doesn't have access to, and only shared between the app and the Authenticator
Limitations
No delegation
In version zero the pubky is the issuer, meaning that the AuthToken is signed by the same key of the pubky. This is to simplify the spec until we have a reason to keep the issuer keys even more secure than being in a mobile app used rarely to authenticate a browser session once in a while.
Having an issuer that isn't exactly the pubky means the issuer themselves need a certificate of delegation signed by the pubky. The problem with that is that you can either lookup that certificate on the Homeserver (making the verification process async and possibly taking too long to timeout) or do what most TLS apps do right now and send the certificates chain with the token. The problem with this is that you then have to deal with the eternal problem of revocation, which basically also forces you to go lookup somewhere making the the verification process async and possibly taking too long before timeout.
Expiration is out of scope
While the token itself can only be used for very brief period, it is immediately exchanged for another authentication mechanism (usually a session ID). Deciding the expiration date of that authentication, if any, is out of the scope of this spec.
The assumption here is that we are authorizing a session to the Homeserver such that the user can always access all active sessions and revoke any session that they don't like, all from the Authenticator app.
Other services are free to choose their authentication system once the homeserver verifies the pubky auth token, whether that is a JWT or a Session with or without expiration and are free to allow the user to manage sessions the way they see fit.