Authentication

Speechwave never asks anyone for a password. There are two ways to sign in, and a third path just for the Chrome extension, which isn’t a web page and can’t use either one.

Magic link

Enter your email, get a one-time link. Clicking it logs you in. The token is single-use and gets deleted the moment it's consumed.

OAuth

Sign in with Google, Microsoft, or GitHub, handled through the Assent library. Works the same way whether it's your first visit or your fiftieth.

Routing

Auth routes
GET    /users/log-in                 UserLive.Login, :new
GET    /users/magic_link/:token      UserSessionController, :magic_link
DELETE /users/log-out                UserSessionController, :delete
GET    /auth/:provider               UserSessionController, :oauth_authorize
GET    /auth/:provider/callback      UserSessionController, :oauth_callback
Magic link walkthrough
1. Submit an email
Browser
/users/log-in
The user enters an email address. The server finds or creates an account for it. A link goes out either way, whether or not the account already existed, so a stranger can't use the response to guess who has a Speechwave account.
2. Email goes out
Browser
Server
Inbox
The server creates a token record and emails the link through Swoosh. The token is short-lived, so an old link sitting in an inbox stops being useful before long.
3. Click the link
Inbox
/users/magic_link/:token
UserSessionController consumes the token. It's single-use: the moment it's checked, it's deleted, so a link can't be replayed.
4. Session starts
Token consumed
/dashboard
A session is created and the browser is redirected to the dashboard, signed in.

OAuth

OAuth covers two different situations with the same provider code:

  • Login: find or create an account by provider and provider account id, or match an existing account by email and link the new provider to it.
  • Connect: an already signed-in user adds another provider from Settings, so they can log in either way later.
Email verification guard

OAuth login requires the provider to claim the email is verified. If that claim is missing or false, the login is rejected. Otherwise a provider could vouch for an email address nobody actually controls.

The Scope struct

Every context function that touches user-owned data takes a %Scope{user: user} as its first argument, so filtering by ownership is explicit instead of something you have to remember. This is a standard Phoenix 1.8 convention, not anything unique to Speechwave, but it’s worth knowing because you’ll see it everywhere in the codebase.

accounts/scope.ex
defmodule Speechwave.Accounts.Scope do
  defstruct user: nil
end

def list_talks(%Scope{user: user}) do
  Talk
  |> where(user_id: ^user.id)
  |> Repo.all()
end

Users and linked identities

FieldTypeNotes
emailstringThe login identity, no password stored alongside it
planatomfree, pro, or org, see Plans and limits
api_keystringA long random key, auto-generated, used by the Chrome extension
FieldTypeNotes
providerstringgoogle, microsoft, or github
uidstringThe provider's account id
user_idreferencesThe linked account

Chrome extension auth

The extension can’t do magic links or OAuth, since it isn’t a web page. Instead, every user gets an API key, auto-generated and visible in Account Settings, and the extension sends it as a join parameter when it connects to the Channel socket. See WebSockets for the exact check chain that key goes through.

Where the code lives


This site uses Just the Docs, a documentation theme for Jekyll.