6.5.4 钉钉登录:企业内部应用与第三方应用
一句话破题
钉钉登录主要面向 B 端场景,分为企业内部应用(员工登录)和第三方应用(SaaS 产品),接入流程略有不同。
核心价值
接入钉钉登录能让你:
- 为企业级产品提供便捷登录
- 获取用户的企业身份信息
- 与钉钉工作台深度集成
两种应用类型
| 类型 | 适用场景 | 用户范围 |
|---|---|---|
| 企业内部应用 | 企业自用系统 | 仅本企业员工 |
| 第三方应用 | SaaS 产品 | 所有钉钉用户 |
企业内部应用接入
前置条件
- 登录钉钉开放平台
- 创建企业内部应用
- 获取 AppKey 和 AppSecret
实现代码
typescript
// app/api/auth/dingtalk/route.ts
export async function GET() {
const state = generateSecureState()
const params = new URLSearchParams({
appid: process.env.DINGTALK_APP_KEY!,
response_type: 'code',
scope: 'openid',
redirect_uri: 'https://your-site.com/api/auth/dingtalk/callback',
state,
prompt: 'consent',
})
return Response.redirect(
`https://login.dingtalk.com/oauth2/auth?${params}`
)
}typescript
// app/api/auth/dingtalk/callback/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const authCode = searchParams.get('authCode')
const state = searchParams.get('state')
if (!verifyState(state)) {
return Response.redirect('/login?error=invalid_state')
}
// 1. 获取用户 access_token
const tokenRes = await fetch('https://api.dingtalk.com/v1.0/oauth2/userAccessToken', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: process.env.DINGTALK_APP_KEY,
clientSecret: process.env.DINGTALK_APP_SECRET,
code: authCode,
grantType: 'authorization_code',
}),
})
const { accessToken } = await tokenRes.json()
// 2. 获取用户信息
const userRes = await fetch('https://api.dingtalk.com/v1.0/contact/users/me', {
headers: { 'x-acs-dingtalk-access-token': accessToken },
})
const userInfo = await userRes.json()
// 3. 创建或关联用户
const user = await findOrCreateUser({
dingtalkUnionId: userInfo.unionId,
dingtalkOpenId: userInfo.openId,
nickname: userInfo.nick,
avatar: userInfo.avatarUrl,
mobile: userInfo.mobile,
})
await createSession(user)
return Response.redirect('/dashboard')
}钉钉的用户标识
| 标识 | 说明 | 跨应用 |
|---|---|---|
openId | 应用内唯一 | 否 |
unionId | 企业内唯一 | 是(同企业) |
userId | 企业通讯录 ID | 需额外权限 |
获取企业信息
如果需要获取用户所在企业信息,需要额外调用接口:
typescript
// 获取企业 access_token
const corpTokenRes = await fetch(
`https://oapi.dingtalk.com/gettoken?appkey=${appKey}&appsecret=${appSecret}`
)
const { access_token: corpToken } = await corpTokenRes.json()
// 根据 unionId 获取 userId
const userIdRes = await fetch(
`https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=${corpToken}`,
{
method: 'POST',
body: JSON.stringify({ unionid: userInfo.unionId }),
}
)
// 获取用户详细信息(包含部门等)
const detailRes = await fetch(
`https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${corpToken}`,
{
method: 'POST',
body: JSON.stringify({ userid }),
}
)避坑指南
新手最容易犯的错
- 混淆新版 API 和旧版 API(推荐使用新版 v1.0)
- 企业内部应用只能本企业员工使用
- unionId 只在同一企业内相同,不同企业不同
- 部分接口需要企业管理员授权
