使用 supabase 注册和登录

使用 supabase 提供的 saas 能力实现注册和登录功能

Posted by Jerry Chen on December 2, 2024

使用 supabase 的后端示例

代码文件

文件清单如下:

1
2
3
.env
package.json
server.js

.env 内容如下:

当我们创建好一个 supabase 项目后,在项目主页会有如下信息:

项目 API 您的 API 由 API 网关保护,每个请求都需要 API 密钥。 您可以使用以下参数来使用 Supabase 客户端库。 项目 URL:XXX API 密钥:XXX

1
2
SUPABASE_URL=XXX
SUPABASE_ANON_KEY=XXX

package.json 内容如下:

Express.js 作为 Web 服务器

@supabase/supabase-js 用于与 Supabase 交互

dotenv 用于管理环境变量

1
2
3
4
5
6
7
8
9
{
  "name": "supabase-auth-demo",
  "version": "1.0.0",
  "dependencies": {
    "@supabase/supabase-js": "^2.39.0",
    "express": "^4.18.2",
    "dotenv": "^16.3.1"
  }
}

server.js 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
require('dotenv').config();
const express = require('express');
const { createClient } = require('@supabase/supabase-js');

const app = express();
app.use(express.json());

// 初始化 Supabase 客户端
const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);

// 注册路由
app.post('/signup', async (req, res) => {
  const { email, password } = req.body;
  
  try {
    const { data, error } = await supabase.auth.signUp({
      email,
      password
    });
    
    if (error) throw error;
    
    res.json({ message: '注册成功', data });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// 登录路由
app.post('/login', async (req, res) => {
  const { email, password } = req.body;
  
  try {
    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password
    });
    
    if (error) throw error;
    
    res.json({ message: '登录成功', data });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`服务器运行在端口 ${PORT}`);
});

使用方法

  1. 首先安装依赖:

    1
    
    npm install
    
  2. 运行服务器:

    在 .env 文件中填入你的 Supabase 项目凭证(可以从 Supabase 项目设置中找到),接着执行指令:

    1
    
    node server.js
    
  3. 测试接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # 注册新用户
    curl -X POST http://localhost:3000/signup \
     -H "Content-Type: application/json" \
     -d '{"email":"test@example.com","password":"your_password"}'
       
    # 登录
    curl -X POST http://localhost:3000/login \
     -H "Content-Type: application/json" \
     -d '{"email":"test@example.com","password":"your_password"}'
    

使用 supabase 的网站基本示例

代码文件

文件清单如下:

只在上一个示例基础上心中一个 html 文件;

1
2
3
4
public/index.html
.env
package.json
server.js

public/index.html 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Supabase 登录演示</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 500px;
            margin: 20px auto;
            padding: 20px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        input {
            width: 100%;
            padding: 8px;
            margin-top: 5px;
        }
        button {
            padding: 10px 15px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
        #message {
            margin-top: 20px;
            padding: 10px;
        }
        .success { color: green; }
        .error { color: red; }
    </style>
</head>
<body>
    <h2>Supabase 认证演示</h2>
    
    <div class="form-group">
        <label for="email">邮箱:</label>
        <input type="email" id="email" required>
    </div>
    
    <div class="form-group">
        <label for="password">密码:</label>
        <input type="password" id="password" required>
    </div>
    
    <button onclick="signup()">注册</button>
    <button onclick="login()">登录</button>
    
    <div id="message"></div>

    <script>
        function showMessage(text, isError = false) {
            const messageDiv = document.getElementById('message');
            messageDiv.textContent = text;
            messageDiv.className = isError ? 'error' : 'success';
        }

        async function makeRequest(endpoint) {
            const email = document.getElementById('email').value;
            const password = document.getElementById('password').value;

            try {
                const response = await fetch(endpoint, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ email, password })
                });

                const data = await response.json();
                
                if (!response.ok) {
                    throw new Error(data.error || '请求失败');
                }

                showMessage(data.message);
                console.log('详细响应:', data);
            } catch (error) {
                showMessage(error.message, true);
            }
        }

        function signup() {
            makeRequest('/signup');
        }

        function login() {
            makeRequest('/login');
        }
    </script>
</body>
</html> 

.env 内容和上一个示例相同;

package.json 内容和上一个示例相同;

server.js 内容只新增一行;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require('dotenv').config();
const express = require('express');
const { createClient } = require('@supabase/supabase-js');

const app = express();
app.use(express.json());
+ app.use(express.static('public'));

// 初始化 Supabase 客户端
const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);

... ...

使用方法

  1. 首先安装依赖:

    1
    
    npm install
    
  2. 运行服务器:

    在 .env 文件中填入你的 Supabase 项目凭证(可以从 Supabase 项目设置中找到),接着执行指令:

    1
    
    node server.js
    
  3. 访问 http://localhost:3000,在打开的页面中尝试注册和登录;

使用 supabase 的网站完整登录登出示例

代码文件

文件清单如下:

只在上一个示例基础上心中一个 html 文件;

1
2
3
4
public/index.html
.env
package.json
server.js

public/index.html 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Supabase 登录演示</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 500px;
            margin: 20px auto;
            padding: 20px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        input {
            width: 100%;
            padding: 8px;
            margin-top: 5px;
        }
        button {
            padding: 10px 15px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
        #message {
            margin-top: 20px;
            padding: 10px;
        }
        .success { color: green; }
        .error { color: red; }
        .hidden {
            display: none;
        }
        nav {
            margin-bottom: 20px;
            border-bottom: 1px solid #ddd;
            padding-bottom: 0;
        }
        nav button {
            margin: 0 5px -1px 0;
            padding: 10px 20px;
            border: 1px solid #ddd;
            background-color: #f8f8f8;
            border-bottom: 1px solid #ddd;
            border-radius: 4px 4px 0 0;
            cursor: pointer;
            outline: none;
            color: #666;
            font-weight: 500;
            font-size: 15px;
        }
        nav button.active {
            background-color: white;
            border-bottom: 1px solid white;
            color: #2E7D32;
            font-weight: bold;
        }
        nav button:hover {
            background-color: #f0f0f0;
            color: #2E7D32;
        }
        nav button.active:hover {
            background-color: white;
        }
    </style>
</head>
<body>
    <nav>
        <button id="loginBtn" class="active" onclick="showLoginForm()">登录</button>
        <button id="signupBtn" onclick="showSignupForm()">注册</button>
    </nav>

    <div id="authForms">
        <h2 id="formTitle">登录</h2>
        
        <div class="form-group">
            <label for="email">邮箱:</label>
            <input type="email" id="email" required>
        </div>
        
        <div class="form-group">
            <label for="password">密码:</label>
            <input type="password" id="password" required>
        </div>
        
        <button id="submitBtn" onclick="handleSubmit()">登录</button>
        
        <div id="message"></div>
    </div>

    <div id="dashboard" class="hidden">
        <h2>欢迎回来</h2>
        <p id="userEmail"></p>
        <button onclick="logout()">登出</button>
    </div>

    <script>
        let currentMode = 'login';
        let accessToken = localStorage.getItem('accessToken');

        // 检查登录状态
        async function checkAuthStatus() {
            if (!accessToken) {
                showAuthForms();
                return;
            }

            try {
                const response = await fetch('/api/auth/status', {
                    headers: {
                        'Authorization': `Bearer ${accessToken}`
                    }
                });

                if (response.ok) {
                    const data = await response.json();
                    showDashboard(data.user);
                } else {
                    localStorage.removeItem('accessToken');
                    showAuthForms();
                }
            } catch (error) {
                console.error('验证状态失败:', error);
                showAuthForms();
            }
        }

        function showLoginForm() {
            currentMode = 'login';
            document.getElementById('formTitle').textContent = '登录';
            document.getElementById('submitBtn').textContent = '登录';
            document.getElementById('loginBtn').classList.add('active');
            document.getElementById('signupBtn').classList.remove('active');
            clearForm();
        }

        function showSignupForm() {
            currentMode = 'signup';
            document.getElementById('formTitle').textContent = '注册';
            document.getElementById('submitBtn').textContent = '注册';
            document.getElementById('loginBtn').classList.remove('active');
            document.getElementById('signupBtn').classList.add('active');
            clearForm();
        }

        function showAuthForms() {
            document.getElementById('authForms').classList.remove('hidden');
            document.getElementById('dashboard').classList.add('hidden');
        }

        function showDashboard(user) {
            document.getElementById('authForms').classList.add('hidden');
            document.getElementById('dashboard').classList.remove('hidden');
            document.getElementById('userEmail').textContent = `邮箱: ${user.email}`;
        }

        function clearForm() {
            document.getElementById('email').value = '';
            document.getElementById('password').value = '';
            document.getElementById('message').textContent = '';
        }

        function showMessage(text, isError = false) {
            const messageDiv = document.getElementById('message');
            messageDiv.textContent = text;
            messageDiv.className = isError ? 'error' : 'success';
        }

        async function handleSubmit() {
            const endpoint = currentMode === 'login' ? '/login' : '/signup';
            const email = document.getElementById('email').value;
            const password = document.getElementById('password').value;

            try {
                const response = await fetch(endpoint, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ email, password })
                });

                const data = await response.json();
                
                if (!response.ok) {
                    throw new Error(data.error || '请求失败');
                }

                if (data.data?.session?.access_token) {
                    localStorage.setItem('accessToken', data.data.session.access_token);
                    accessToken = data.data.session.access_token;
                    showDashboard(data.data.user);
                }

                showMessage(data.message);
            } catch (error) {
                showMessage(error.message, true);
            }
        }

        async function logout() {
            try {
                const response = await fetch('/api/auth/logout', {
                    method: 'POST',
                    headers: {
                        'Authorization': `Bearer ${accessToken}`
                    }
                });

                if (response.ok) {
                    localStorage.removeItem('accessToken');
                    accessToken = null;
                    showAuthForms();
                    showLoginForm();
                } else {
                    throw new Error('登出失败');
                }
            } catch (error) {
                showMessage(error.message, true);
            }
        }

        // 页面加载时检查登录状态
        checkAuthStatus();
    </script>
</body>
</html>

.env 内容和上一个示例相同;

package.json 内容和上一个示例相同;

server.js 添加了会话验证中间件;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
require('dotenv').config();
const express = require('express');
const { createClient } = require('@supabase/supabase-js');

const app = express();
app.use(express.json());
app.use(express.static('public'));

// 初始化 Supabase 客户端
const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);

// 添加会话验证中间件
const authenticateToken = async (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: '未登录' });
  }

  try {
    const { data: { user }, error } = await supabase.auth.getUser(token);
    if (error) throw error;
    req.user = user;
    next();
  } catch (error) {
    return res.status(401).json({ error: '无效的令牌' });
  }
};

// 添加检查登录状态的接口
app.get('/api/auth/status', authenticateToken, (req, res) => {
  res.json({ 
    isAuthenticated: true, 
    user: req.user 
  });
});

// 添加登出接口
app.post('/api/auth/logout', authenticateToken, async (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];
  const { error } = await supabase.auth.signOut();
  if (error) {
    return res.status(400).json({ error: error.message });
  }
  res.json({ message: '已成功登出' });
});

// 注册路由
app.post('/signup', async (req, res) => {
  const { email, password } = req.body;
  
  try {
    const { data, error } = await supabase.auth.signUp({
      email,
      password
    });
    
    if (error) throw error;
    
    res.json({ message: '注册成功', data });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// 登录路由
app.post('/login', async (req, res) => {
  const { email, password } = req.body;
  
  try {
    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password
    });
    
    if (error) throw error;
    
    res.json({ message: '登录成功', data });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`服务器运行在端口 ${PORT}`);
});

使用方法

  1. 首先安装依赖:

    1
    
    npm install
    
  2. 运行服务器:

    在 .env 文件中填入你的 Supabase 项目凭证(可以从 Supabase 项目设置中找到),接着执行指令:

    1
    
    node server.js
    
  3. 访问 http://localhost:3000,在打开的页面中尝试注册和登录,登录后会有登出按钮和实现;