关于 umbraco-mfa-login-provider
用于构建 Umbraco 后台 MFA 设置界面的技能,指导生成提供程序清单和配置元素;后端 C# 提供程序需另行配置。
主要功能
- 01配置 MFA 提供程序清单
- 02生成自定义配置界面
- 03使用内置默认界面
- 04配合配置后端提供程序
- 05按文档指导实现
适用场景
- 01为 Umbraco 2FA 提供程序添加设置界面
- 02创建自定义 MFA 配置元素
- 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。
文档
实现前请务必获取最新文档:
- 双因素身份验证:https://docs.umbraco.com/umbraco-cms/run-in-production/security/two-factor-authentication
- 扩展类型:https://docs.umbraco.com/umbraco-cms/extend-your-project/backoffice-extensions/extending-overview/extension-types
- 基础知识:https://docs.umbraco.com/umbraco-cms/extend-your-project/backoffice-extensions/foundation
工作流
- 获取文档 - 使用 WebFetch 访问上述 URL
- 提出问题 - 使用哪种 2FA 方法?需要二维码设置吗?需要自定义验证 UI 吗?
- 配置后端 - 先设置 C#
ITwoFactorProvider - 生成前端文件 - 创建 manifest 和配置元素
- 说明 - 展示创建的内容以及测试方法
最小示例
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 接口
| 属性 | 类型 | 说明 |
|---|---|---|
providerName | string | 提供程序标识符 |
displayName | string | 易于理解的提供程序名称 |
callback | Function | 调用此函数并传入代码/密钥以进行验证 |
close | Function | 关闭设置模态框 |
回调函数
callback(providerName: string, code: string, secret: string): Promise<{ error?: string }>
如果验证失败,返回包含 error 属性的对象;验证成功时返回空对象。
就是这样!务必获取最新文档,保持示例精简,并生成完整可运行的代码。
评论
支持 Markdown