diff --git a/.github/ISSUE_TEMPLATE/2_bug_provider.yml b/.github/ISSUE_TEMPLATE/2_bug_provider.yml index 1ee4409a23..738ae771f8 100644 --- a/.github/ISSUE_TEMPLATE/2_bug_provider.yml +++ b/.github/ISSUE_TEMPLATE/2_bug_provider.yml @@ -29,6 +29,7 @@ body: - "Atlassian" - "Auth0" - "Authentik" + - "AutoSend" - "Azure Active Directory" - "Azure Active Directory B2C" - "Azure DevOps" diff --git a/docs/pages/data/manifest.json b/docs/pages/data/manifest.json index 7fef03e029..a5666f6359 100644 --- a/docs/pages/data/manifest.json +++ b/docs/pages/data/manifest.json @@ -125,6 +125,7 @@ "zoom": "Zoom" }, "providersEmail": { + "autosend": "AutoSend", "forwardemail": "Forward Email", "resend": "Resend", "sendgrid": "Sendgrid", diff --git a/docs/pages/getting-started/authentication/email.mdx b/docs/pages/getting-started/authentication/email.mdx index 7e0248efda..6e0e79e254 100644 --- a/docs/pages/getting-started/authentication/email.mdx +++ b/docs/pages/getting-started/authentication/email.mdx @@ -41,6 +41,15 @@ This login mechanism starts by the user providing their email address at the log
Forward Email
+ + +
AutoSend
+
+ Mailgun + + + +### AutoSend Setup + + + +### Database Adapter + +Please make sure you've [setup a database adapter](/getting-started/database), as mentioned earlier, +a database is required for passwordless login to work as verification tokens need to be stored. + +### Setup Environment Variables + +Auth.js will automatically pick up these if formatted like the example above. +You can [also use a different name for the environment variables](/guides/environment-variables#oauth-variables) if needed, but then you’ll need to pass them to the provider manually. + +```bash filename=".env" +AUTH_AUTOSEND_KEY=AS_XXXXXXXXXXXXXXXXXXXXXXXXX...XXX +``` + +### Setup Provider + +Let’s enable `AutoSend` as a sign in option in our Auth.js configuration. You’ll have to import the `AutoSend` provider from the package and pass it to the providers array we setup earlier in the Auth.js config file: + + + + +```ts filename="./auth.ts" +import NextAuth from "next-auth" +import AutoSend from "next-auth/providers/autosend" + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers: [AutoSend({ from: "no-reply@company.com" })], +}) +``` + + + + +```ts filename="/src/routes/plugin@auth.ts" +import { QwikAuth$ } from "@auth/qwik" +import AutoSend from "@auth/qwik/providers/autosend" + +export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$( + () => ({ + providers: [AutoSend({ from: "no-reply@company.com" })], + }) +) +``` + + + + +```ts filename="./src/auth.ts" +import SvelteKitAuth from "@auth/sveltekit" +import AutoSend from "@auth/sveltekit/providers/autosend" + +export const { handle, signIn, signOut } = SvelteKitAuth({ + providers: [AutoSend({ from: "no-reply@company.com" })], +}) +``` + +```ts filename="./src/hooks.server.ts" +export { handle } from "./auth" +``` + + + + +### Add Signin Button + +Next, we can add a signin button somewhere in your application like the Navbar. This will send an email to the user containing the magic link to sign in. + + + + +```tsx filename="./components/sign-in.tsx" +import { signIn } from "../../auth.ts" + +export function SignIn() { + return ( +
{ + "use server" + await signIn("autosend", formData) + }} + > + + +
+ ) +} +``` + +
+ + +```tsx filename="./components/sign-in.tsx" +"use client" +import { signIn } from "next-auth/react" + +export function SignIn() { + const autosendAction = (formData: FormData) => { + signIn("autosend", formData) + } + + return ( +
+ + +
+ ) +} +``` + +
+ + +```ts filename="./components/sign-in.tsx" +import { component$ } from "@builder.io/qwik" +import { useSignIn } from "./plugin@auth" + +export default component$(() => { + const signInSig = useSignIn() + + return ( + + ) +}) +``` + + + + +```html filename="src/routes/+page.svelte" + + +
+ +
+``` + +
+
+ +
+ + + Check out the [AutoSend provider docs + page](/getting-started/providers/autosend#customization) to learn how to + change the look and feel of the emails the user receives to sign in. + + +For more information on this provider go to the [AutoSend docs page](/getting-started/providers/autosend). + +
+ + +# AutoSend Provider + +## Overview + +The AutoSend provider uses email to send "magic links" that contain URLs with verification tokens that can be used to sign in. + +Adding support for signing in via email in addition to one or more OAuth services provides a way for users to sign in if they lose access to their OAuth account (e.g. if it is locked or deleted). + +The AutoSend provider can be used in conjunction with (or instead of) one or more OAuth providers. + +### How it works + +On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in. + +If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email. + + + The AutoSend provider can be used with both JSON Web Token and database + managed sessions, however **you must configure a database** to use it. It is + not possible to enable email sign in without using a database. + + +## Configuration + +1. First, you'll need to add and verify a sender domain in the [AutoSend dashboard](https://autosend.com). This is required by AutoSend, and the address you use in the `from` provider option must use a verified domain. + +2. Next, you will have to generate an API key in the [AutoSend API Key](https://autosend.com/account/api-key). You can save this API key as the `AUTH_AUTOSEND_KEY` environment variable. + +```sh +AUTH_AUTOSEND_KEY=AS_XXXXXXXXXXXXXXXXXXXXXXXXX...XXX +``` + +If you name your environment variable `AUTH_AUTOSEND_KEY`, the provider will pick it up automatically and your Auth.js configuration object can be simpler. If you'd like to rename it to something else, however, you'll have to manually pass it into the provider in your Auth.js configuration. + + + + +```ts filename="./auth.ts" +import NextAuth from "next-auth" +import AutoSend from "next-auth/providers/autosend" + +export const { handlers, auth, signIn, signOut } = NextAuth({ + adapter: ..., + providers: [ + AutoSend({ + // If your environment variable is named differently than default + apiKey: AUTH_AUTOSEND_KEY, + from: "no-reply@company.com" + }), + ], +}) +``` + + + + +```ts filename="/src/routes/plugin@auth.ts" +import { QwikAuth$ } from "@auth/qwik" +import AutoSend from "@auth/qwik/providers/autosend" + +export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$( + () => ({ + providers: [ + AutoSend({ + // If your environment variable is named differently than default + apiKey: import.meta.env.AUTH_AUTOSEND_KEY, + from: "no-reply@company.com", + }), + ], + }) +) +``` + + + + +```ts filename="./src/auth.ts" +import { SvelteKitAuth } from "@auth/sveltekit" +import AutoSend from "@auth/sveltekit/providers/autosend" +import { env } from "$env/dynamic/private" + +export const { handle, signIn, signOut } = SvelteKitAuth({ + adapter: ..., + providers: [ + AutoSend({ + // If your environment variable is named differently than default + apiKey: env.AUTH_AUTOSEND_KEY, + from: "no-reply@company.com", + }), + ], +}) +``` + + + + +4. Do not forget to setup one of the [database adapters](https://authjs.dev/getting-started/database) for storing the Email verification token. + +5. You can now start the sign-in process with an email address at `/api/auth/signin`. + +A user account (i.e. an entry in the `Users` table) will not be created for the user until the first time they verify their email address. If an email address is already associated with an account, the user will be signed in to that account when they click the link in magic link email and use up the verification token. + +## Customization + +### Email Body + +You can fully customize the sign in email that is sent by passing a custom function as the `sendVerificationRequest` option to `AutoSend()`. + +```js {7} filename="./auth.ts" +import NextAuth from "next-auth" +import AutoSend from "next-auth/providers/autosend" + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers: [ + AutoSend({ + apiKey: process.env.AUTH_AUTOSEND_KEY, + from: process.env.EMAIL_FROM, + sendVerificationRequest({ + identifier: email, + url, + provider: { apiKey, from }, + }) { + // your function + }, + }), + ], +}) +``` + +As an example, the following shows the source for our built-in `sendVerificationRequest()` method. Notice that we're rendering the HTML (`html()`) and making the network call (`fetch()`) to AutoSend to actually do the sending here in this method. AutoSend expects `from` and `to` as objects (`{ email }`), so the built-in method wraps the `from` string in an object. + +```ts filename="./lib/authSendRequest.ts" {4, 14} +export async function sendVerificationRequest(params) { + const { identifier: to, provider, url, theme } = params + const { host } = new URL(url) + const res = await fetch("https://api.autosend.com/v1/mails/send", { + method: "POST", + headers: { + Authorization: `Bearer ${provider.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + from: { email: provider.from }, + to: { email: to }, + subject: `Sign in to ${host}`, + html: html({ url, host, theme }), + text: text({ url, host }), + bypassSuppressions: true, + }), + }) + + if (!res.ok) + throw new Error("AutoSend error: " + JSON.stringify(await res.json())) +} +``` + + + If you want to generate great looking emails with React that are compatible + with many email clients, check out [mjml](https://mjml.io) or + [react-email](https://react.email) + + +### Verification Tokens + +By default, we are generating a random verification token. You can define a `generateVerificationToken` method in your provider options if you want to override it: + +```ts filename="./auth.ts" +import NextAuth from "next-auth" +import AutoSend from "next-auth/providers/autosend" + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers: [ + AutoSend({ + async generateVerificationToken() { + return crypto.randomUUID() + }, + }), + ], +}) +``` + +### Normalizing Email Addresses + +By default, Auth.js will normalize the email address. It treats the address as case-insensitive (which is technically not compliant to the [RFC 2821 spec](https://datatracker.ietf.org/doc/html/rfc2821), but in practice this causes more problems than it solves, i.e. when looking up users by e-mail from databases.) and also removes any secondary email address that may have been passed in as a comma-separated list. You can apply your own normalization via the `normalizeIdentifier` method on the `AutoSend` provider. The following example shows the default behavior: + +```ts filename="./auth.ts" +import NextAuth from "next-auth" +import AutoSend from "next-auth/providers/autosend" + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers: [ + AutoSend({ + normalizeIdentifier(identifier: string): string { + // Get the first two elements only, + // separated by `@` from user input. + let [local, domain] = identifier.toLowerCase().trim().split("@") + // The part before "@" can contain a "," + // but we remove it on the domain part + domain = domain.split(",")[0] + return `${local}@${domain}` + + // You can also throw an error, which will redirect the user + // to the sign-in page with error=EmailSignin in the URL + // if (identifier.split("@").length > 2) { + // throw new Error("Only one email allowed") + // } + }, + }), + ], +}) +``` + + + Always make sure this returns a single e-mail address, even if multiple ones + were passed in. + diff --git a/docs/public/img/providers/autosend.svg b/docs/public/img/providers/autosend.svg new file mode 100644 index 0000000000..2e5c261b9a --- /dev/null +++ b/docs/public/img/providers/autosend.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/core/scripts/generate-providers.js b/packages/core/scripts/generate-providers.js index ff74012730..9245fd0647 100644 --- a/packages/core/scripts/generate-providers.js +++ b/packages/core/scripts/generate-providers.js @@ -7,6 +7,7 @@ const files = readdirSync(providersPath, "utf8") // TODO: Autogenerate const emailProvidersFile = [ + "autosend", "email", "forwardemail", "mailgun", diff --git a/packages/core/src/providers/autosend.ts b/packages/core/src/providers/autosend.ts new file mode 100644 index 0000000000..95edc79144 --- /dev/null +++ b/packages/core/src/providers/autosend.ts @@ -0,0 +1,46 @@ +/** + *
+ * Built-in AutoSend integration. + * + * + * + *
+ * + * @module providers/autosend + */ + +import type { EmailConfig, EmailUserConfig } from "./index.js" +import { html, text } from "../lib/utils/email.js" + +export default function AutoSend(config: EmailUserConfig): EmailConfig { + return { + id: "autosend", + type: "email", + name: "AutoSend", + from: "Auth.js ", + maxAge: 24 * 60 * 60, + async sendVerificationRequest(params) { + const { identifier: to, provider, url, theme } = params + const { host } = new URL(url) + const res = await fetch("https://api.autosend.com/v1/mails/send", { + method: "POST", + headers: { + Authorization: `Bearer ${provider.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + from: { email: provider.from }, + to: { email: to }, + subject: `Sign in to ${host}`, + html: html({ url, host, theme }), + text: text({ url, host }), + bypassSuppressions: true, + }), + }) + + if (!res.ok) + throw new Error("AutoSend error: " + JSON.stringify(await res.json())) + }, + options: config, + } +}