Implement Log In with Atmosphere #9
Labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Blocks
#12 Implement user invitations
puregarlic/microclimate
#14 Add user roles
puregarlic/microclimate
Reference
puregarlic/microclimate#9
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Users will sign in using AT Protocol OAuth2, and get a signed session cookie from the server. The server owner DID will need to be specified in environment variables, so they can log in initially to manage the server.
This issue is important for getting individual user identities; currently we're randomly generating a user identity every time you join a channel. There's no consistency, so no way to, for example, tie user preferences to a specific user, or a way to gate access to the microclimate server.
AT Protocol OAuth is fairly complicated to implement from scratch, but fortunately there's a comprehensive crate as part of the Jacquard project. We'll be able to use that to keep the implementation on the simpler side.
My proposal for the authentication process looks like this:
actor-typeaheadweb component, or something custom inspired by it.client_id's domain in reverse-NSID format. We cannot predict the URL that a user might host their server at, so we cannot guarantee a matching deep-link scheme on platforms that require registration of custom URL schemes at compile time. Hence, the polling process by the client.get_channels.That's broadly what I'm thinking. We'll probably want a refresh mechanism as well, and a rolling keyset system for our JWKS, but that's possibly another issue as well.
The overall design looks solid. A few things worth considering before implementation:
DID validation at callback time. In step 7, the server should verify that the DID returned by the OAuth callback matches the identifier the user provided in step 1. Without this check, a user could supply one handle in the form but complete the OAuth flow as a different account.
Pending session TTL. The server accumulates pending login sessions until they resolve. Worth building in an expiry (10 minutes seems reasonable) so stale sessions from abandoned flows get cleaned up. Especially relevant for the mobile disconnection case — if the client never reconnects, the session would otherwise sit indefinitely.
Stream timeout vs. session reuse. Related to the above: if the client's polling stream times out before the user completes the OAuth flow in the browser, what happens when the user does eventually approve it? If the session ID is still valid server-side, the client should be able to re-open the stream and receive the JWT. Worth making sure the session lifecycle is explicit about when it expires vs. when the stream does.
JWKS complexity. Since the server both issues and validates these JWTs, a rolling JWKS endpoint may be more infrastructure than the MVP needs. A stable asymmetric key pair (or a rotating HMAC secret) is simpler and sufficient as long as third parties never need to validate our tokens independently. The rolling keyset is worth revisiting if that changes.
Proto additions. This will need at least two new RPCs — something like InitiateLogin(identifier) -> { oauth_url, session_id } and either a server-streaming PollLoginStatus(session_id) -> { status, jwt } or a unary GetLoginResult(session_id) -> { jwt } for the reconnect case. Worth sketching those out early since they'll shape both the server implementation and the client flow.
Implementation note: once the server interceptor is validating the JWT on ChannelService calls, GetChannelTokenRequest.username becomes redundant — the server should extract the user's identity from the validated token rather than trusting the client to supply it. This should be cleaned up as part of the PR for this issue.