You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Mastodon API的OAuth获取用户邮箱?

Mastodon OAuth认证无法获取用户邮箱的问题

我正在给一个.NET Core应用添加基于Mastodon API的OAuth认证,调用/api/v1/accounts/verify_credentials端点时,只能拿到"id"和"display_name"字段,拿不到邮箱属性,目前只能用"acct"参数替代。我已经配置了"read:accounts"和"admin:read:accounts"权限范围,想确认有没有能返回当前用户邮箱的Mastodon API端点,查过官方文档没找到相关内容。

相关代码实现:

builder.Services.AddAuthentication()
    .AddMicrosoftAccount("Microsoft", "Microsoft", microsoftOptions =>
    {
        microsoftOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
        microsoftOptions.ClientId = _appSettings.Authentication.Microsoft.ClientId;
        microsoftOptions.ClientSecret = _appSettings.Authentication.Microsoft.ClientSecret;
    })
    .AddGoogle("Google", "Google", googleOptions =>
    {
        googleOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
        googleOptions.ClientId = _appSettings.Authentication.Google.ClientId;
        googleOptions.ClientSecret = _appSettings.Authentication.Google.ClientSecret;
    })
    .AddGitHub("GitHub", "GitHub", githubOptions =>
    {
        githubOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
        githubOptions.ClientId = _appSettings.Authentication.GitHub.ClientId;
        githubOptions.ClientSecret = _appSettings.Authentication.GitHub.ClientSecret;
    })
    .AddOAuth("Fosstodon", "Fosstodon", fosstodonOptions =>
    {
        fosstodonOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;

        fosstodonOptions.ClientId = _appSettings.Authentication.Fosstodon.ClientId;
        fosstodonOptions.ClientSecret = _appSettings.Authentication.Fosstodon.ClientSecret;
        fosstodonOptions.CallbackPath = new PathString("/signin-fosstodon");

        fosstodonOptions.AuthorizationEndpoint = _appSettings.Authentication.Fosstodon.AuthorizationEndpoint;
        fosstodonOptions.TokenEndpoint = _appSettings.Authentication.Fosstodon.TokenEndpoint;
        fosstodonOptions.UserInformationEndpoint = _appSettings.Authentication.Fosstodon.UserInformationEndpoint;

        fosstodonOptions.SaveTokens = true;
        fosstodonOptions.Scope.Add("read:accounts");
        fosstodonOptions.Scope.Add("admin:read:accounts");

        fosstodonOptions.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
        fosstodonOptions.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
        fosstodonOptions.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");

        fosstodonOptions.Events = new OAuthEvents
        {
            OnCreatingTicket = async context =>
            {
                var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
                request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);

                var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
                response.EnsureSuccessStatusCode();

                var user = JObject.Parse(await response.Content.ReadAsStringAsync());

                var identifier = user.Value<string>("id")?.Clean();
                if (!string.IsNullOrEmpty(identifier))
                {
                    context.Identity?.AddClaim(new Claim(
                        ClaimTypes.NameIdentifier, identifier,
                        ClaimValueTypes.String, context.Options.ClaimsIssuer));
                }

                var userName = user.Value<string>("display_name")?.Clean();
                if (!string.IsNullOrEmpty(userName))
                {
                    context.Identity?.AddClaim(new Claim(
                        ClaimTypes.Name, userName,
                        ClaimValueTypes.String, context.Options.ClaimsIssuer));
                }

                var userEmail = user.Value<string>("acct")?.Clean();
                if (!string.IsNullOrEmpty(userEmail))
                {
                    context.Identity?.AddClaim(new Claim(
                        ClaimTypes.Email, userEmail,
                        ClaimValueTypes.String, context.Options.ClaimsIssuer));
                }
            }
        };
    });
解答

核心结论

Mastodon的公开API不会返回用户邮箱地址,哪怕请求了read:accounts或admin:read:accounts权限。这是出于用户隐私保护的设计——邮箱属于敏感信息,Mastodon默认不会通过OAuth接口对外暴露。

关键原因

  • 隐私优先设计:Mastodon作为去中心化社交平台,用户邮箱仅用于账号验证、密码重置等内部操作,不允许第三方应用通过OAuth获取。
  • 权限范围限制:read:accounts权限仅能获取公开的账号信息(如ID、显示名、用户名acct);admin:read:accounts是服务器管理员专属接口,仅对本地管理员账号有效,且同样不会返回普通用户的邮箱。

替代方案

你目前用acct字段作为邮箱替代是合理的,因为acct格式通常为用户名@实例域名,可以作为用户的唯一标识。如果应用必须获取邮箱,只有两种可行路径:

  • 引导用户手动输入并验证邮箱;
  • 如果你控制自己的Mastodon服务器,可以修改服务器代码或插件自定义返回邮箱字段,但这不符合标准API规范,无法兼容其他公共实例。

代码优化建议

既然API不会返回email字段,建议移除无效的Claim映射:

// 移除该行,因为Mastodon API不会返回email字段
// fosstodonOptions.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");

内容的提问来源于stack exchange,提问作者Chris Cavell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 10:05:21