mcpField
U
umbraco-mfa-login-provider

umbraco-mfa-login-provider

github.com/umbraco安全27392 次安装6 周前更新
代码仓库

关于 umbraco-mfa-login-provider

用于构建 Umbraco 后台 MFA 设置界面的技能,指导生成提供程序清单和配置元素;后端 C# 提供程序需另行配置。

主要功能

  1. 01配置 MFA 提供程序清单
  2. 02生成自定义配置界面
  3. 03使用内置默认界面
  4. 04配合配置后端提供程序
  5. 05按文档指导实现

适用场景

  1. 01为 Umbraco 2FA 提供程序添加设置界面
  2. 02创建自定义 MFA 配置元素
  3. 03将前端清单关联到后端提供程序

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/umbraco/umbraco-cms-backoffice-skills --skill umbraco-mfa-login-provider

安装后在 Claude Code 中输入/umbraco-mfa-login-provider确认已加载。

详细介绍

Umbraco 多重身份验证登录提供程序

这是什么?

多重身份验证登录提供程序是 Umbraco 中用于双因素身份验证(2FA)的 UI 组件。它为用户提供启用、停用和配置 2FA 提供程序(例如 Google Authenticator、短信验证码)的界面。必须单独在 C# 中配置后端 ITwoFactorProvider——此扩展类型负责前端设置和配置 UI。

文档

实现前请务必获取最新文档:

工作流

  1. 获取文档 - 使用 WebFetch 访问上述 URL
  2. 提出问题 - 使用哪种 2FA 方法?需要二维码设置吗?需要自定义验证 UI 吗?
  3. 配置后端 - 先设置 C# ITwoFactorProvider
  4. 生成前端文件 - 创建 manifest 和配置元素
  5. 说明 - 展示创建的内容以及测试方法

最小示例

Manifest(umbraco-package.json)
{
  "name": "My MFA Provider",
  "extensions": [
    {
      "type": "mfaLoginProvider",
      "alias": "My.MfaProvider.Authenticator",
      "name": "Authenticator App MFA",
      "forProviderName": "Umbraco.GoogleAuthenticator",
      "element": "/App_Plugins/MyMfa/mfa-setup.js",
      "meta": {
        "label": "Authenticator App"
      }
    }
  ]
}
Manifest(TypeScript)
import type { ManifestMfaLoginProvider } from '@umbraco-cms/backoffice/extension-registry';

const manifest: ManifestMfaLoginProvider = {
  type: 'mfaLoginProvider',
  alias: 'My.MfaProvider.Authenticator',
  name: 'Authenticator MFA Provider',
  forProviderName: 'Umbraco.GoogleAuthenticator', // Must match backend provider name
  element: () => import('./mfa-setup.element.js'),
  meta: {
    label: 'Authenticator App',
  },
};

export const manifests = [manifest];
MFA 设置元素(mfa-setup.element.ts)
import { html, css, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import type { UmbMfaProviderConfigurationElementProps } from '@umbraco-cms/backoffice/user';

@customElement('my-mfa-setup')
export class MyMfaSetupElement extends UmbLitElement implements UmbMfaProviderConfigurationElementProps {
  @property({ type: String })
  providerName = '';

  @property({ type: String })
  displayName = '';

  @property({ attribute: false })
  callback!: (providerName: string, code: string, secret: string) => Promise<{ error?: string }>;

  @property({ attribute: false })
  close!: () => void;

  @state()
  private _loading = true;

  @state()
  private _secret = '';

  @state()
  private _qrCodeUrl = '';

  @state()
  private _code = '';

  @state()
  private _submitting = false;

  #notificationContext?: typeof UMB_NOTIFICATION_CONTEXT.TYPE;

  constructor() {
    super();
    this.consumeContext(UMB_NOTIFICATION_CONTEXT, (context) => {
      this.#notificationContext = context;
    });
  }

  async connectedCallback() {
    super.connectedCallback();
    await this.#loadSetupData();
  }

  async #loadSetupData() {
    try {
      // Fetch setup data from API
      const response = await fetch(`/umbraco/management/api/v1/user/2fa/${this.providerName}/setup`);
      const data = await response.json();

      this._secret = data.secret;
      this._qrCodeUrl = data.qrCodeSetupImageUrl;
    } catch (error) {
      this.#notificationContext?.peek('danger', { data: { message: 'Failed to load setup data' } });
    } finally {
      this._loading = false;
    }
  }

  async #handleSubmit(e: SubmitEvent) {
    e.preventDefault();

    if (!this._code || this._code.length !== 6) {
      this.#notificationContext?.peek('warning', { data: { message: 'Please enter a 6-digit code' } });
      return;
    }

    this._submitting = true;

    try {
      const result = await this.callback(this.providerName, this._code, this._secret);

      if (result.error) {
        this.#notificationContext?.peek('danger', { data: { message: result.error } });
      } else {
        this.#notificationContext?.peek('positive', { data: { message: '2FA enabled successfully' } });
        this.close();
      }
    } finally {
      this._submitting = false;
    }
  }

  render() {
    if (this._loading) {
      return html`<uui-loader></uui-loader>`;
    }

    return html`
      <uui-form>
        <form @submit=${this.#handleSubmit}>
          <umb-body-layout headline="Set up ${this.displayName}">
            <div id="main">
              <p>Scan this QR code with your authenticator app:</p>

              <div class="qr-container">
                <img src=${this._qrCodeUrl} alt="QR Code" />
              </div>

              <p>Or enter this secret manually: <code>${this._secret}</code></p>

              <uui-form-layout-item>
                <uui-label slot="label" for="code" required>Verification Code</uui-label>
                <uui-input
                  id="code"
                  type="text"
                  inputmode="numeric"
                  pattern="[0-9]*"
                  maxlength="6"
                  placeholder="000000"
                  .value=${this._code}
                  @input=${(e: InputEvent) => (this._code = (e.target as HTMLInputElement).value)}
                  required
                ></uui-input>
              </uui-form-layout-item>
            </div>

            <div slot="actions">
              <uui-button
                label="Cancel"
                look="secondary"
                @click=${this.close}
              ></uui-button>
              <uui-button
                type="submit"
                label="Enable"
                look="primary"
                color="positive"
                ?state=${this._submitting ? 'waiting' : undefined}
              ></uui-button>
            </div>
          </umb-body-layout>
        </form>
      </uui-form>
    `;
  }

  static styles = css`
    #main {
      max-width: 400px;
      margin: 0 auto;
      text-align: center;
    }

    .qr-container {
      margin: var(--uui-size-space-5) 0;
    }

    .qr-container img {
      max-width: 200px;
      border: 1px solid var(--uui-color-border);
      border-radius: var(--uui-border-radius);
    }

    code {
      display: block;
      margin: var(--uui-size-space-3) 0;
      padding: var(--uui-size-space-2);
      background: var(--uui-color-surface-alt);
      border-radius: var(--uui-border-radius);
      font-family: monospace;
      word-break: break-all;
    }
  `;
}

export default MyMfaSetupElement;

declare global {
  interface HTMLElementTagNameMap {
    'my-mfa-setup': MyMfaSetupElement;
  }
}
使用默认 MFA 元素
// If you don't need custom UI, you can use the built-in default element
// Just register the manifest without a custom element - Umbraco provides a default
const manifest: ManifestMfaLoginProvider = {
  type: 'mfaLoginProvider',
  alias: 'My.MfaProvider.Default',
  name: 'Default MFA Provider',
  forProviderName: 'Umbraco.GoogleAuthenticator',
  // No element specified - uses default
  meta: {
    label: 'Authenticator App',
  },
};
后端 C# 配置(供参考)
// ITwoFactorProvider implementation
public class GoogleAuthenticatorProvider : ITwoFactorProvider
{
    public string ProviderName => "Umbraco.GoogleAuthenticator";

    public Task<bool> ValidateTwoFactorPIN(string secret, string code)
    {
        var twoFactorAuthenticator = new TwoFactorAuthenticator();
        return Task.FromResult(
            twoFactorAuthenticator.ValidateTwoFactorPIN(secret, code)
        );
    }

    public Task<bool> ValidateTwoFactorSetup(string secret, string code)
    {
        return ValidateTwoFactorPIN(secret, code);
    }

    public async Task<object> GetSetupDataAsync(Guid userKey, IMemberService memberService)
    {
        var secret = Base32Encoding.ToString(Guid.NewGuid().ToByteArray());
        var authenticator = new TwoFactorAuthenticator();
        var setupInfo = authenticator.GenerateSetupCode(
            "My App",
            userKey.ToString(),
            secret,
            false
        );

        return new
        {
            secret,
            qrCodeSetupImageUrl = setupInfo.QrCodeSetupImageUrl
        };
    }
}

// Register in Composer
public class MfaComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        var identityBuilder = new BackOfficeIdentityBuilder(builder.Services);
        identityBuilder.AddTwoFactorProvider<GoogleAuthenticatorProvider>(
            GoogleAuthenticatorProvider.Name
        );
    }
}

元素 Props 接口

属性类型说明
providerNamestring提供程序标识符
displayNamestring易于理解的提供程序名称
callbackFunction调用此函数并传入代码/密钥以进行验证
closeFunction关闭设置模态框

回调函数

callback(providerName: string, code: string, secret: string): Promise<{ error?: string }>

如果验证失败,返回包含 error 属性的对象;验证成功时返回空对象。

就是这样!务必获取最新文档,保持示例精简,并生成完整可运行的代码。

评论

支持 Markdown