Lucia

Multiple OAuth providers

Database

To support multiple OAuth sign-in methods, we can store the OAuth credentials in its own OAuth account table instead of the user table. Here, the combination of provider_id and provider_user_id should be unique (composite primary key).

column type description
provider_id string OAuth provider
provider_user_id string OAuth user ID
user_id string user ID

Here's an example with SQLite:

CREATE TABLE oauth_account (
    provider_id TEXT NOT NULL,
    provider_user_id TEXT NOT NULL,
    user_id TEXT NOT NULL,
    PRIMARY KEY (provider_id, provider_user_id),
    FOREIGN KEY (user_id) REFERENCES user(id)
);

We can then remove the github_id column etc from the user table.

Validating callback

Instead of the user table, we can now use the OAuth account table to check if a user is already registered. If not, in a transaction, create the user and OAuth account.

const tokens = await githubAuth.validateAuthorizationCode(code);
const githubUser = await githubAuth.getUser(tokens.accessToken);

const existingAccount = await db
	.table("oauth_account")
	.where("provider_id", "=", "github")
	.where("provider_user_id", "=", githubUser.id)
	.get();

if (existingAccount) {
	const session = await lucia.createSession(existingAccount.user_id, {});

	// ...
}

const userId = generateIdFromEntropySize(10); // 16 characters long

await db.beginTransaction();
await db.table("user").insert({
	id: userId,
	username: githubUser.login
});
await db.table("oauth_account").insert({
	provider_id: "github",
	provider_user_id: githubUser.id,
	user_id: userId
});
await db.commit();

const session = await lucia.createSession(userId, {});

// ...