Skip to content

Commit b0bc8b9

Browse files
committed
feat: Implement ASWebAuthenticationSession flow for iOS login
1 parent 57ddba3 commit b0bc8b9

2 files changed

Lines changed: 106 additions & 1 deletion

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# iOS 应用网页登录集成指南
2+
3+
本文档旨在为 Web 开发团队提供与 iOS 端集成的详细说明,以实现通过 `ASWebAuthenticationSession` 进行的用户认证流程。
4+
5+
## 1. 认证流程概述
6+
7+
当 iOS 用户点击“通过网页登录”按钮时,应用会打开一个安全的应用内浏览器(由 `ASWebAuthenticationSession` 提供),并加载您指定的网页。用户在该网页上完成认证后,网页服务器需要将认证凭据(例如 Token)通过特定的 URL Scheme 回传给 iOS 应用。iOS 应用会拦截这个 URL,解析出凭据,并完成登录。
8+
9+
**核心流程**
10+
11+
1. iOS App 打开 `https://rote.ink/login?type=ioslogin`
12+
2. 用户在网页上完成登录操作(例如,输入用户名/密码,或通过第三方 OAuth 登录)。
13+
3. Web 服务器验证成功后,**必须通过 HTTP 302 重定向** 到一个特定的 URL,该 URL 使用 iOS App 的自定义 Scheme `rote://`
14+
4. iOS App 监听到这个重定向,关闭浏览器,并从 URL 中提取 Token。
15+
16+
---
17+
18+
## 2. Web 端需要实现的关键步骤
19+
20+
### 步骤 1:创建登录入口页面
21+
22+
您需要确保以下 URL 是一个有效的、可供用户登录的页面:
23+
24+
```
25+
https://rote.ink/login?type=ioslogin
26+
```
27+
28+
iOS 应用会直接打开这个地址。您可以利用 `type=ioslogin` 这个查询参数来为来自 App 的请求展示特定的 UI(例如,隐藏“注册”按钮或导航栏)。
29+
30+
### 步骤 2:处理用户认证
31+
32+
用户在此页面上进行的所有认证操作(如表单提交、与第三方 OAuth 提供商交互等)均由您的服务器处理。这部分是标准的 Web 认证流程。
33+
34+
### 步骤 3:成功后的重定向(最关键的一步)
35+
36+
当用户在您的服务器上成功认证后,服务器 **必须** 生成一个认证 Token (例如 JWT),然后构造一个回调 URL,并执行 **HTTP 302 重定向**
37+
38+
**回调 URL 的格式必须如下**
39+
40+
```
41+
rote://callback?token=YOUR_GENERATED_TOKEN
42+
```
43+
44+
**URL 构成详解**
45+
46+
- **Scheme**: `rote://`
47+
- 这是在 iOS 应用中预先定义好的,用于唤醒应用的协议。**必须**是这个值。
48+
- **Host**: `callback`
49+
- 这部分可以自定义(例如 `auth`, `login-success` 等),但建议使用 `callback`。iOS 端目前没有严格校验 Host,但会解析整个 URL。
50+
- **Query Parameter**: `?token=YOUR_GENERATED_TOKEN`
51+
- 这是最重要的部分。iOS 应用会专门查找名为 `token` 的查询参数来获取认证凭据。**参数名必须是 `token`**
52+
- `YOUR_GENERATED_TOKEN` 就是您服务器生成的实际用户 Token。
53+
54+
**服务器端实现示例 (使用 Express.js)**
55+
56+
```javascript
57+
const express = require("express");
58+
const app = express();
59+
60+
// 伪代码:处理登录请求的路由
61+
app.post("/login-handler", (req, res) => {
62+
// 1. 验证用户凭据 (req.body.username, req.body.password)
63+
const userIsValid = validateUser(req.body);
64+
65+
if (userIsValid) {
66+
// 2. 如果验证成功,生成 Token
67+
const jwtToken = generateJwtForUser(req.body.username);
68+
69+
// 3. 构造回调 URL
70+
const callbackUrl = `rote://callback?token=${jwtToken}`;
71+
72+
// 4. 执行重定向
73+
res.redirect(callbackUrl);
74+
} else {
75+
// 处理登录失败的情况
76+
res.status(401).send("Authentication failed");
77+
}
78+
});
79+
80+
// ... 其他代码 ...
81+
```
82+
83+
---
84+
85+
## 3. 流程总结
86+
87+
| 步骤 | 操作方 | 描述 |
88+
| :--- | :--------- | :------------------------------------------------------------------------------------------------------------- |
89+
| 1 | iOS App | 用户点击登录,打开 `https://rote.ink/login?type=ioslogin` |
90+
| 2 | 用户 | 在网页上输入凭据,点击登录 |
91+
| 3 | Web Server | 验证用户凭据,生成 Token |
92+
| 4 | Web Server | **关键**:返回 HTTP 302 响应,`Location` 头指向 `rote://callback?token=...` |
93+
| 5 | iOS App | `ASWebAuthenticationSession` 自动拦截重定向,关闭浏览器,将回调 URL `rote://callback?token=...` 传递给应用代码 |
94+
| 6 | iOS App | 解析 URL,提取 `token` 参数,保存 Token 并完成登录 |
95+
96+
请确保 Web 端的实现在用户成功登录后,严格按照上述格式执行重定向。这是整个认证流程能够闭环的关键。

web/src/pages/login/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { useAPIGet } from '@/utils/fetcher';
1919
import { useAtomValue, useSetAtom } from 'jotai';
2020
import { useEffect, useState } from 'react';
2121
import { useTranslation } from 'react-i18next';
22-
import { Link, useNavigate } from 'react-router-dom';
22+
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
2323
import { toast } from 'sonner';
2424
import { z } from 'zod';
2525
const { safeRoutes } = mainJson;
@@ -40,6 +40,7 @@ function Login() {
4040

4141
const [disbled, setDisbled] = useState(false);
4242
const [activeTab, setActiveTab] = useState('login');
43+
const [searchParams] = useSearchParams();
4344

4445
const [loginData, setLoginData] = useState({
4546
username: '',
@@ -96,6 +97,14 @@ function Login() {
9697
setDisbled(false);
9798
// 登录成功后刷新全局 profile
9899
loadProfile();
100+
101+
// 检查是否为 iOS web 登录流程
102+
if (searchParams.get('type') === 'ioslogin') {
103+
const callbackUrl = `rote://callback?token=${accessToken}`;
104+
window.location.href = callbackUrl;
105+
return;
106+
}
107+
99108
navigate('/home');
100109
})
101110
.catch((err: any) => {

0 commit comments

Comments
 (0)