Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] Support magic links in SignInPage #4085

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
31ea476
feat: Add `nodemailer` provider to `SignInPage`
bharatkashyap Sep 13, 2024
c952172
Merge branch 'master' into feat/magic-link
bharatkashyap Sep 13, 2024
131b443
fix: eslint
bharatkashyap Sep 13, 2024
65e0949
Merge branch 'feat/magic-link' of github.com:bharatkashyap/mui-toolpa…
bharatkashyap Sep 13, 2024
c9e3956
Merge branch 'master' into feat/magic-link
bharatkashyap Sep 14, 2024
2c61b88
Merge branch 'master' of github.com:mui/mui-toolpad into feat/magic-link
bharatkashyap Sep 17, 2024
bbdddef
Merge branch 'master' into feat/magic-link
bharatkashyap Sep 17, 2024
4d7a335
Merge branch 'master' into feat/magic-link
bharatkashyap Sep 18, 2024
c0796ab
Merge branch 'master' into feat/magic-link
bharatkashyap Sep 18, 2024
000f694
Merge branch 'master' into feat/magic-link
bharatkashyap Sep 20, 2024
7882158
Merge branch 'master' into feat/magic-link
bharatkashyap Sep 20, 2024
3b6309d
Merge branch 'master' into feat/magic-link
bharatkashyap Sep 20, 2024
2dd6554
Merge branch 'master' of github.com:mui/mui-toolpad into feat/magic-link
bharatkashyap Sep 30, 2024
31ba678
Merge branch 'master' of github.com:mui/mui-toolpad into feat/magic-link
bharatkashyap Oct 10, 2024
70ef2aa
fix: Add example link to docs
bharatkashyap Oct 10, 2024
d390f71
fix: CI
bharatkashyap Oct 11, 2024
4532fb2
Merge branch 'master' of github.com:mui/mui-toolpad into feat/magic-link
bharatkashyap Oct 11, 2024
110a06e
Merge branch 'master' of github.com:mui/mui-toolpad into feat/magic-link
bharatkashyap Oct 11, 2024
55b316f
fix: Simplify magic links docs and fix example
bharatkashyap Oct 11, 2024
cf82dc8
fix: lint
bharatkashyap Oct 11, 2024
b139284
fix: Add test
bharatkashyap Oct 11, 2024
2ee806b
wip: Test this build
bharatkashyap Oct 11, 2024
02073b5
Merge branch 'master' into feat/magic-link
bharatkashyap Oct 11, 2024
f80bfab
fix: CI
bharatkashyap Oct 11, 2024
b4ff718
Merge branch 'feat/magic-link' of github.com:bharatkashyap/mui-toolpa…
bharatkashyap Oct 11, 2024
dc5ebf4
fix: CI, test build
bharatkashyap Oct 12, 2024
c0bf20b
fix: CI
bharatkashyap Oct 12, 2024
2422a15
fix: Test docker deployment and add it to example
bharatkashyap Oct 13, 2024
56ceca0
Merge branch 'master' of github.com:mui/mui-toolpad into feat/magic-link
bharatkashyap Oct 13, 2024
7f68521
fix: CI
bharatkashyap Oct 13, 2024
56b4466
fix: Minor tweaks
bharatkashyap Oct 14, 2024
91d95f0
fix: Jan review
bharatkashyap Oct 15, 2024
f139401
fix: CI
bharatkashyap Oct 15, 2024
16f2a3b
Update docs/data/toolpad/core/components/sign-in-page/sign-in-page.md
bharatkashyap Oct 16, 2024
5a1b0b4
Update docs/data/toolpad/core/components/sign-in-page/sign-in-page.md
bharatkashyap Oct 16, 2024
040fcaf
fix: Jan review
bharatkashyap Oct 16, 2024
dc9f688
fix: CI
bharatkashyap Oct 16, 2024
d534daa
Merge branch 'master' of github.com:mui/mui-toolpad into feat/magic-link
bharatkashyap Oct 16, 2024
22de185
Merge branch 'master' of github.com:mui/mui-toolpad into feat/magic-link
bharatkashyap Oct 18, 2024
ffde5f0
fix: lint
bharatkashyap Oct 18, 2024
c72c511
Merge branch 'master' of github.com:mui/mui-toolpad into feat/magic-link
bharatkashyap Oct 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react';
import { SignInPage } from '@toolpad/core/SignInPage';
import { AppProvider } from '@toolpad/core/AppProvider';
import { useTheme } from '@mui/material/styles';

const providers = [{ id: 'nodemailer', name: 'Email' }];

const signIn = async (provider) => {
const promise = new Promise((resolve) => {
setTimeout(() => {
console.log(`Sign in with ${provider.id}`);
// preview-start
resolve({
success: 'Check your email for a verification link.',
});
// preview-end
}, 500);
});
return promise;
};

export default function MagicLinkAlertSignInPage() {
const theme = useTheme();
return (
// preview-start
<AppProvider theme={theme}>
<SignInPage signIn={signIn} providers={providers} />
</AppProvider>
// preview-end
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import {
AuthProvider,
SignInPage,
SupportedAuthProvider,
AuthResponse,
} from '@toolpad/core/SignInPage';
import { AppProvider } from '@toolpad/core/AppProvider';
import { useTheme } from '@mui/material/styles';

const providers: { id: SupportedAuthProvider; name: string }[] = [
{ id: 'nodemailer', name: 'Email' },
];

const signIn: (provider: AuthProvider) => Promise<AuthResponse> = async (
provider,
) => {
const promise = new Promise<AuthResponse>((resolve) => {
setTimeout(() => {
console.log(`Sign in with ${provider.id}`);
// preview-start
resolve({
success: 'Check your email for a verification link.',
});
// preview-end
}, 500);
});
return promise;
};

export default function MagicLinkAlertSignInPage() {
const theme = useTheme();
return (
// preview-start
<AppProvider theme={theme}>
<SignInPage signIn={signIn} providers={providers} />
</AppProvider>
// preview-end
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
resolve({
success: 'Check your email for a verification link.',
});

// ...

<AppProvider theme={theme}>
<SignInPage signIn={signIn} providers={providers} />
</AppProvider>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react';
import { SignInPage } from '@toolpad/core/SignInPage';
import { AppProvider } from '@toolpad/core/AppProvider';
import { useTheme } from '@mui/material/styles';

// preview-start
const providers = [{ id: 'nodemailer', name: 'Email' }];

// preview-end

const signIn = async (provider) => {
const promise = new Promise((resolve) => {
setTimeout(() => {
console.log(`Sign in with ${provider.id}`);
resolve();
}, 500);
});
return promise;
};

export default function MagicLinkSignInPage() {
const theme = useTheme();
return (
// preview-start
<AppProvider theme={theme}>
<SignInPage signIn={signIn} providers={providers} />
</AppProvider>
// preview-end
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as React from 'react';
import {
AuthProvider,
SignInPage,
SupportedAuthProvider,
} from '@toolpad/core/SignInPage';
import { AppProvider } from '@toolpad/core/AppProvider';
import { useTheme } from '@mui/material/styles';

// preview-start
const providers: { id: SupportedAuthProvider; name: string }[] = [
{ id: 'nodemailer', name: 'Email' },
];
// preview-end

const signIn: (provider: AuthProvider) => void = async (provider) => {
const promise = new Promise<void>((resolve) => {
setTimeout(() => {
console.log(`Sign in with ${provider.id}`);
resolve();
}, 500);
});
return promise;
};

export default function MagicLinkSignInPage() {
const theme = useTheme();
return (
// preview-start
<AppProvider theme={theme}>
<SignInPage signIn={signIn} providers={providers} />
</AppProvider>
// preview-end
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const providers: { id: SupportedAuthProvider; name: string }[] = [
{ id: 'nodemailer', name: 'Email' },
];

// ...

<AppProvider theme={theme}>
<SignInPage signIn={signIn} providers={providers} />
</AppProvider>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { AppProvider } from '@toolpad/core/AppProvider';
import { SignInPage } from '@toolpad/core/SignInPage';
import { useTheme } from '@mui/material/styles';

// preview-start
const providers = [
{ id: 'github', name: 'GitHub' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { AppProvider } from '@toolpad/core/AppProvider';
import { SignInPage, type AuthProvider } from '@toolpad/core/SignInPage';
import { useTheme } from '@mui/material/styles';

// preview-start
const providers = [
{ id: 'github', name: 'GitHub' },
Expand Down
26 changes: 22 additions & 4 deletions docs/data/toolpad/core/components/sign-in-page/sign-in-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The `SignInPage` component can be set up with an OAuth provider by passing in a

:::info

The following providers are supported and maintained by default:
The following OAuth providers are supported and maintained by default:

- Google
- GitHub
Expand All @@ -44,11 +44,29 @@ The following providers are supported and maintained by default:
- Twitch
- Discord
- Keycloak
- Credentials (username/password)

Find details on how to set up each provider in the [Auth.js documentation](https://authjs.dev/getting-started/authentication/oauth).
:::

## Magic Link
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a default success alert that would already show in this demo?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a separate demo on the Alerts just below this one, so I think this one is okay? If you think strongly that we should merge them, I can do that

Copy link
Member

@apedroferreira apedroferreira Oct 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a huge deal, it's just that the first demo doesn't provide any feedback when you press the submit button so maybe that will feel a bit weird if it's the first thing a user tries.
But I just noticed that other demos in the same page work the same... maybe they could always show a standard browser alert or something (instead of a console log that most people will miss), but I guess that's something you can add at any time, doesn't need to block this PR.


The `SignIn` page component supports magic links. To enable this, you have to set up a provider such as Auth.js NodeMailer. See more details in the Auth.js docs on [database setup for email](https://authjs.dev/getting-started/authentication/email) and [Nodemailer configuration](https://authjs.dev/getting-started/providers/nodemailer/).

To render a magic link form, pass in a provider with `nodemailer` as the `id` property.

{{"demo": "MagicLinkSignInPage.js", "iframe": true, "height": 400}}

### Alerts

The `SignInPage` component can display a success alert if the email is sent successfully. You can enable this by passing a `success` property in the
response object of the `signIn` prop.

{{"demo": "MagicLinkAlertSignInPage.js", "iframe": true, "height": 400}}

:::info
Check out the complete [Next.js Auth.js Magic Link example](https://github.com/mui/mui-toolpad/tree/master/examples/core-auth-nextjs-email/) example for a working implementation of a magic link sign-in page with Auth.js, Nodemailer, Prisma and PostgreSQL.
:::

## Passkey

The `SignInPage` component can be set up to use [Passkeys](https://passkeys.dev) by passing in a provider with `passkey` as the `id`:
Expand All @@ -64,7 +82,7 @@ The [Toolpad Core Passkey example app](https://github.com/mui/mui-toolpad/tree/m
## Credentials

:::warning
It is recommended to use the OAuth or Passkey provider for more robust maintenance, support, and security.
The Credentials provider is not the most secure way to authenticate users. It's recommended to use any of the other providers for a more robust solution.
:::

To render a username password form, pass in a provider with `credentials` as the `id` property. The `signIn` function accepts a `formData` parameter in this case.
Expand Down Expand Up @@ -231,4 +249,4 @@ The `SignInPage` component has versions with different layouts for authenticatio

## 🚧 Other authentication Flows

The `SignInPage` will be accompanied by other components to allow users to sign up, verify emails and reset passwords. This is in progress.
Besides the `SignInPage` , the team is planning work on several other components that enable new workflows such as [sign up](https://github.com/mui/toolpad/issues/4068) and [password reset](https://github.com/mui/toolpad/issues/4265).
2 changes: 1 addition & 1 deletion docs/pages/toolpad/core/api/sign-in-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"providers": {
"type": {
"name": "arrayOf",
"description": "Array&lt;{ id: 'apple'<br>&#124;&nbsp;'auth0'<br>&#124;&nbsp;'cognito'<br>&#124;&nbsp;'credentials'<br>&#124;&nbsp;'discord'<br>&#124;&nbsp;'facebook'<br>&#124;&nbsp;'fusionauth'<br>&#124;&nbsp;'github'<br>&#124;&nbsp;'gitlab'<br>&#124;&nbsp;'google'<br>&#124;&nbsp;'instagram'<br>&#124;&nbsp;'keycloak'<br>&#124;&nbsp;'line'<br>&#124;&nbsp;'linkedin'<br>&#124;&nbsp;'microsoft-entra-id'<br>&#124;&nbsp;'okta'<br>&#124;&nbsp;'passkey'<br>&#124;&nbsp;'slack'<br>&#124;&nbsp;'spotify'<br>&#124;&nbsp;'tiktok'<br>&#124;&nbsp;'twitch'<br>&#124;&nbsp;'twitter', name: string }&gt;"
"description": "Array&lt;{ id: 'apple'<br>&#124;&nbsp;'auth0'<br>&#124;&nbsp;'cognito'<br>&#124;&nbsp;'credentials'<br>&#124;&nbsp;'discord'<br>&#124;&nbsp;'facebook'<br>&#124;&nbsp;'fusionauth'<br>&#124;&nbsp;'github'<br>&#124;&nbsp;'gitlab'<br>&#124;&nbsp;'google'<br>&#124;&nbsp;'instagram'<br>&#124;&nbsp;'keycloak'<br>&#124;&nbsp;'line'<br>&#124;&nbsp;'linkedin'<br>&#124;&nbsp;'microsoft-entra-id'<br>&#124;&nbsp;'nodemailer'<br>&#124;&nbsp;'okta'<br>&#124;&nbsp;'passkey'<br>&#124;&nbsp;'slack'<br>&#124;&nbsp;'spotify'<br>&#124;&nbsp;'tiktok'<br>&#124;&nbsp;'twitch'<br>&#124;&nbsp;'twitter', name: string }&gt;"
},
"default": "[]"
},
Expand Down
3 changes: 3 additions & 0 deletions examples/core-auth-nextjs-email/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
5 changes: 5 additions & 0 deletions examples/core-auth-nextjs-email/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
45 changes: 45 additions & 0 deletions examples/core-auth-nextjs-email/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Use Node.js 20 Alpine as the base image
FROM node:20-alpine AS builder

# Set working directory
WORKDIR /app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy all files
COPY . .

# Build the Next.js app
RUN npm run build

# Start a new stage for a smaller final image
FROM node:20-alpine AS runner

WORKDIR /app

# Copy built assets from the builder stage
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/src/prisma ./prisma

# Set environment variables
ENV NODE_ENV production
ENV PORT 3000

# Copy the entrypoint script
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# Set the entrypoint
ENTRYPOINT ["/entrypoint.sh"]

# Expose the port Next.js runs on
EXPOSE 3000

# Run the Next.js app
CMD ["npm", "start"]
34 changes: 34 additions & 0 deletions examples/core-auth-nextjs-email/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Toolpad Core Next.js App Router app with email provider

This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

Check warning on line 34 in examples/core-auth-nextjs-email/README.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.We] Try to avoid using first-person plural like 'our'. Raw Output: {"message": "[Google.We] Try to avoid using first-person plural like 'our'.", "location": {"path": "examples/core-auth-nextjs-email/README.md", "range": {"start": {"line": 34, "column": 11}}}, "severity": "WARNING"}
52 changes: 52 additions & 0 deletions examples/core-auth-nextjs-email/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
version: '3.8'

services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- '3000:3000'
environment:
- DATABASE_URL=${DATABASE_URL}
- AUTH_URL=${AUTH_URL}
- AUTH_TRUST_HOST=true
- NODE_ENV=production
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
- AUTH_SECRET=${AUTH_SECRET}
- EMAIL_SERVER_HOST=${EMAIL_SERVER_HOST}
- EMAIL_SERVER_PORT=${EMAIL_SERVER_PORT}
- EMAIL_SERVER_USER=${EMAIL_SERVER_USER}
- EMAIL_SERVER_PASSWORD=${EMAIL_SERVER_PASSWORD}
- EMAIL_FROM=${EMAIL_FROM}
depends_on:
db:
condition: service_healthy
networks:
- app-network

db:
image: postgres:13
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
ports:
- '${POSTGRES_PORT}:5432'
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
interval: 5s
timeout: 5s
retries: 5
networks:
- app-network

volumes:
postgres_data:

networks:
app-network:
driver: bridge
Loading
Loading