ホームページ  >  記事  >  ウェブフロントエンド  >  ユーザー管理を社内から製品に移行: なぜそれを行ったのか、そして何を学んだのか

ユーザー管理を社内から製品に移行: なぜそれを行ったのか、そして何を学んだのか

PHPz
PHPzオリジナル
2024-08-13 06:40:09375ブラウズ

目次

  • TL;DR
  • 私たちの主な目的
  • 研究と評価
  • 実装
  • JWT 戦略 vs. クラーク戦略
  • Clerk と Novu 間の同期
    • ユーザーと組織の同期
    • Clerk vs Novu に保存されるもの
  • Enterprise Edition プロバイダーの導入
    • AuthService と AuthModule - 動的インジェクション
    • リポジトリ - ユーザー、組織、メンバー
    • コントローラー
    • このアプローチの問題点
  • エンドポイントの変更
  • 考慮すべき重要なポイントと避けるべき重要なポイント
  • チームのスポットライト
  • 概要
  • 後知恵ボーナスポイント

この投稿は Novu によって提供されました

Moving User Management from In-House to a Product: Why We Did It and What We Learned ノヴフク / ノヴ

オープンソースの通知プラットフォーム。埋め込み可能な通知センター、電子メール、プッシュ、Slack の統合。

Moving User Management from In-House to a Product: Why We Did It and What We Learned

Moving User Management from In-House to a Product: Why We Did It and What We Learned Moving User Management from In-House to a Product: Why We Did It and What We Learned Moving User Management from In-House to a Product: Why We Did It and What We Learned

開発者向けのオープンソース通知インフラストラクチャ

単一の API でマルチチャネル通知を管理する究極のサービス


ドキュメントを確認する »

バグを報告する · リクエスト機能 · Discordに参加してください · ロードマップ · × · 通知ディレクトリ

⭐️なぜノヴなのか?

Novu は、アプリ内、プッシュ、電子メール、SMS、チャットなどの複数のチャネルを通じて通知を簡単に送信できるようにする統合 API を提供します。 Novu を使用すると、カスタム ワークフローを作成し、各チャネルの条件を定義して、通知が可能な限り最も効果的な方法で配信されるようにすることができます。

✨特徴

  • ?すべてのメッセージング プロバイダー (アプリ内、電子メール、SMS、プッシュ、チャット) に対応する単一の API
  • ?フルマネージドの GitOps Flow、CI からデプロイ
  • ? Zod または JSON スキーマを使用してワークフローとステップの検証を定義します
  • ? React Email/Maizzle/MJML の統合
  • ?高度なレイアウトとデザイン管理を実現するCMSを搭載
  • ?単一のダッシュボードでマルチチャネル メッセージをデバッグおよび分析します
  • ?埋め込み可能な通知センター…
GitHub で表示

TL;DR

Novu は、SAML シングル サインオン (SSO) 機能、OAuth プロバイダーとしての Google と GitHub、多要素認証、ロールベースのアカウント制御 (RBAC) を提供する基礎を築くユーザー管理ソリューション (認証インフラストラクチャ) として Clerk を実装しました。 )など。

Adam という名前の開発者が、プラットフォーム エンジニアの Denis の強力な支援を受けて、これを実装しました。


Moving User Management from In-House to a Product: Why We Did It and What We Learned

ほとんどのプロジェクトと同様、このプロジェクトもバックログから始まりました。数十人の顧客がそれを要求し、ロードマップでその要求に大きく賛成票を投じるまでには至りませんでした。

通知インフラストラクチャ ソリューションとして、アプリとアーキテクチャの一部には、サインアップ、ログイン、セッション ログからユーザーを管理することが含まれており、ユーザーがチーム メンバーを組織に招待し、各ロールのアクセスを管理できるようになります。

すべては優先順位に関するものです。 Novu の主な焦点は、通知とメッセージ管理に関連するすべてのことを解決して、ユーザーがその必要をなくすことです。したがって、私たちは通知ワークフローの構築と管理に最適なエクスペリエンスを提供し、開発者が時間を保護できるようにし、製品チームとマーケティング チーム間のコラボレーションを円滑にすることにサイクルを費やしています。
効果的なユーザー管理は、私たちが注力している「核となる価値」には当てはまりません。

通知のエンジニアリングの負担を当社の専門家に任せてほしいと期待しているのと同じように、効果的なユーザー管理のエンジニアリングの負担を Clerk の専門家に任せました。

言うまでもなく、私たちのチームは、カスタムメイドで適切に設計されたアーキテクチャに基づいて、初日から社内で優れた認証および認可インフラストラクチャを構築しました。

レベルが上がるにつれて、通知開発エクスペリエンスを完璧にすることにさらに重点を置きます。

私たちは、開発者やエンジニアが車輪の再発明を避け、Nov に通知を処理させることを期待しています。これは、当社が製品の他の側面 (データベースには MongoDB、支払いには Stripe など) に実証済み、テスト済みの最先端のソリューションを活用することを選択するのと同じことです。ユーザー管理の事務員。私たちは話し合いを進めます。


私たちの主な目的

ユーザーにとって安全で使いやすいエクスペリエンスを作成します。

このプロジェクトの最初の草案の概要を説明するとき、簡潔で単純に見えるかもしれません。場合によっては、週末もあれば完成できるような印象さえ与えるかもしれません。

初期ドラフトのチェックリスト:

  • OAuth プロバイダー (GitHub、Google)
  • SAML SSO
  • 安全なセッション管理
  • RBAC
  • マジックリンク認証

最初のドラフトが変更されていない場合、プロジェクトは十分なフィードバックと入力を受け取っていないことに注意してください。当然のことながら、リストは長くなってしまいました。

実際のチェックリスト:

  • ユーザー認証情報を使用してサインアップします
  • OAuth プロバイダー (Github、Google) にサインアップします
  • ユーザー認証情報を使用してサインインします
  • OAuth プロバイダー (Github、Google) でサインインします
  • SSO (SAML) でサインイン
  • Nov CLI からサインイン
  • Vercel Marketplace からサインイン/アップします
  • 組織を作成する
  • 組織管理
  • ユーザー管理 (ユーザー情報、資格情報などの更新)
  • MFA/2FA (SMS/電子メールによる OTP、TOTP、パスキー、生体認証など)
  • 招待状
  • RBAC: 管理者と編集者の 2 つの役割
    • admin = 管理者は、Web プラットフォーム上の任意のページにアクセスして操作できます (つまり、チーム メンバーや設定も含みます)
    • editor = 編集者の役割は「メイン コンテンツ マネージャー」 (別名プロダクト マネージャーまたはマーケティング マネージャー) のままです

研究と評価

プロジェクトの範囲を特定したら、次のステップは調査を実施し、望ましい結果を達成するために必要なリソースを評価することです。

このプロセスには以下が含まれます:

  • 現在の状態と製品の各層を明確に理解します。

    • 依存関係
    • エンドポイント
    • 建築
    • クライアント層のコンポーネントと表現 (フロントエンド)
    • テスト

    その他

  • 移行仕様の概要 (社内に残り、遮断する必要があるもの)

  • 下位互換性

  • 元同僚から同様のプロジェクトへの参照を見つけて、そのプロセスや推奨事項から学んでください

  • オープンソース ソリューションを試して見つけてください

  • ベンダー (サードパーティ ソリューション) があるかどうかを見つけて比較します。

その他

別のブログ投稿では、サービス企業としてのサードパーティ ソリューション (または製品) やインフラストラクチャをどのように評価および比較するかを検討します。

不十分な調査や不正確な評価は、通常、技術的負債や将来のリソースの損失につながります。たとえば、機能追加やメンテナンス時のエンジニアリング時間など、全体のリファクタリングが必要になります。したがって、各オプションの隠れたコストを検索してください。

経験豊富なチーム リーダーは、各オプションの投資収益率 (ROI) を評価する方法を知っており、ビジネスにとって最善の決定を下すのに役立ちます。

それがまさに私たちが Clerk と出会った経緯です。彼らのソリューションは当社のユースケースのほとんどをカバーしており、ビジネスの観点から、ユーザーと組織のレイヤーを管理するためにそれらを実装することの ROI は理にかなっています。


実装

Novu サービスには、多くのマイクロサービスと次のような側面が含まれています。

  • 通知チャネル (SMS、電子メール、アプリ内、プッシュ、チャットなど)、
  • 通知オーケストレーション (クロスデバイス同期、ダイジェスト エンジン、遅延、タイムゾーン認識など)
  • 通知の可観測性 (デバッグ、インサイトなど)
  • 通知コンテンツ管理 (エディター、ブランディング、レイアウト、翻訳、変数管理など)
  • エンドユーザー管理 (ユーザー設定、購読者、トピック、セグメント、購読管理など)
  • アカウント管理 (SSO、ロールベースのアクセス制御、マルチテナンシー、請求など)

以下の図は、Clerk を実装する前の Novu ユーザーと組織の認証と認可のみに焦点を当てた、Nov の API 構造の簡略化されたバージョンを示しています。

Moving User Management from In-House to a Product: Why We Did It and What We Learned

私たちは MongoDB を使用して、Novu が必要とするすべてのデータ、つまりすべてのユーザー、組織、テナント、サブスクライバー、トピック…つまりすべてを保存します。

Clerk はユーザーを管理するための独自のデータベースを持っているため、データベース間の移行と同期を非常に慎重かつ正確に処理する必要がありました。


JWT 戦略とクラーク戦略

確認する必要がある主な点の 1 つは、Nov を使用するときにユーザーのセッションを中断しないように UserSessionData オブジェクトが変更されないことです。互換性は維持されるはずです。

ここで jwt.stratgy.ts ファイルの例を確認できます:

//jwt.stratgy.ts
import type http from 'http';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ApiAuthSchemeEnum, HttpRequestHeaderKeysEnum, UserSessionData } from '@novu/shared';
import { AuthService, Instrument } from '@novu/application-generic';
import { EnvironmentRepository } from '@novu/dal';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService, private environmentRepository: EnvironmentRepository) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
      passReqToCallback: true,
    });
  }
  @Instrument()
  async validate(req: http.IncomingMessage, session: UserSessionData) {
    // Set the scheme to Bearer, meaning the user is authenticated via a JWT coming from Dashboard
    session.scheme = ApiAuthSchemeEnum.BEARER;

    const user = await this.authService.validateUser(session);
    if (!user) {
      throw new UnauthorizedException();
    }

    await this.resolveEnvironmentId(req, session);

    return session;
  }

  @Instrument()
  async resolveEnvironmentId(req: http.IncomingMessage, session: UserSessionData) {
    // Fetch the environmentId from the request header
    const environmentIdFromHeader =
      (req.headers[HttpRequestHeaderKeysEnum.NOVU_ENVIRONMENT_ID.toLowerCase()] as string) || '';

    /*
     * Ensure backwards compatibility with existing JWTs that contain environmentId
     * or cached SPA versions of Dashboard as there is no guarantee all current users
     * will have environmentId in localStorage instantly after the deployment.
     */
    const environmentIdFromLegacyAuthToken = session.environmentId;

    let currentEnvironmentId = '';

    if (environmentIdFromLegacyAuthToken) {
      currentEnvironmentId = environmentIdFromLegacyAuthToken;
    } else {
      const environments = await this.environmentRepository.findOrganizationEnvironments(session.organizationId);
      const environmentIds = environments.map((env) => env._id);
      const developmentEnvironmentId = environments.find((env) => env.name === 'Development')?._id || '';

      currentEnvironmentId = developmentEnvironmentId;

      if (environmentIds.includes(environmentIdFromHeader)) {
        currentEnvironmentId = environmentIdFromHeader;
      }
    }

    session.environmentId = currentEnvironmentId;
  }
}

Moving User Management from In-House to a Product: Why We Did It and What We Learned

アプリの残りの部分との互換性を維持するには、JWT ペイロードを Clerk から以前の既存の JWT 形式に変換する必要がありました。

これが私たちのやり方です:

async validate(payload: ClerkJwtPayload): Promise<IJwtClaims> {
  const jwtClaims: IJwtClaims = {
    // first time its clerk_id, after sync its novu internal id
    _id: payload.externalId || payload._id,
    firstName: payload.firstName,
    lastName: payload.lastName,
    email: payload.email,
    profilePicture: payload.profilePicture,
    // first time its clerk id, after sync its novu internal id
    organizationId: payload.externalOrgId || payload.org_id,
    environmentId: payload.environmentId,
    roles: payload.org_role ? [payload.org_role.replace('org:', '')] : [],
    exp: payload.exp,
  };

  return jwtClaims;
}

ここで、clarker.strategy.ts ファイルの例を確認できます。

import type http from 'http';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';
import {
  ApiAuthSchemeEnum,
  ClerkJwtPayload,
  HttpRequestHeaderKeysEnum,
  PassportStrategyEnum,
  UserSessionData,
} from '@novu/shared';
import { EnvironmentRepository, EnvironmentEntity } from '@novu/dal';
import { LinkEntitiesService } from '../services/link-entities.service';

@Injectable()
export class ClerkStrategy extends PassportStrategy(Strategy, PassportStrategyEnum.JWT_CLERK) {
  constructor(private environmentRepository: EnvironmentRepository, private linkEntitiesService: LinkEntitiesService) {
    super({
      // ...configuration details
    });
  }

  async validate(req: http.IncomingMessage, payload: ClerkJwtPayload) {
    const { internalUserId, internalOrgId } = await this.linkEntitiesService.linkInternalExternalEntities(req, payload);

    const session: UserSessionData = {
      _id: internalUserId,
      firstName: payload.firstName,
      lastName: payload.lastName,
      email: payload.email,
      profilePicture: payload.profilePicture,
      organizationId: internalOrgId,
      roles: payload.org_role ? [payload.org_role.replace('org:', '')] : [],
      exp: payload.exp,
      iss: payload.iss,
      scheme: ApiAuthSchemeEnum.BEARER,
      environmentId: undefined,
    };

    await this.resolveEnvironmentId(req, session);

    return session;
  }

  // Other functions...
}

Moving User Management from In-House to a Product: Why We Did It and What We Learned


Clerk と Novu 間の同期

理想的には、ユーザーや組織などの作成と取得に Clerk のみを使用することが目標ですが、残念ながら、ユーザーや組織に関する一部のメタデータを効率的な方法で保存およびクエリする必要があるため、完全には不可能です。

これは、Nov の組織リポジトリ内のメソッドの例です:

  async findPartnerConfigurationDetails(organizationId: string, userId: string, configurationId: string) {
    const organizationIds = await this.getUsersMembersOrganizationIds(userId);

    return await this.find(
      {
        _id: { $in: organizationIds },
        'partnerConfigurations.configurationId': configurationId,
      },
      { 'partnerConfigurations.$': 1 }
    );
  }

このメソッドは、さまざまな MongoDB 固有の構成要素を使用してドキュメントをフィルター処理します。このようなクエリ用のデータベースではないため、Clerk を使用してパフォーマンスの高い方法でこれを再現することはできません。

私たちができることは、組織に関するこれらのメタデータを MongoDB 組織コレクションに保存し、externalId を使用してそのコレクションを Clerk データベースにリンク/同期することです。

Moving User Management from In-House to a Product: Why We Did It and What We Learned

これで、Clerk と MongoDB の両方を組み合わせて、必要に応じてメタデータをクエリできるようになりました。

async findPartnerConfigurationDetails(
  organizationId: string,
  userId: string,
  configurationId: string
): Promise<OrganizationEntity[]> {
  const clerkOrganizations = await this.getUsersMembersOrganizations(userId);

  return await this.communityOrganizationRepository.find(
    {
      _id: { $in: clerkOrganizations.map((org) => org.id) },
      'partnerConfigurations.configurationId': configurationId,
    },
    { 'partnerConfigurations.$': 1 }
  );
}

private async getUsersMembersOrganizations(userId: string): Promise<Organization[]> {
  const userOrgMemberships = await this.clerkClient.users.getOrganizationMembershipList({
    userId,
  });

  return userOrgMemberships.data.map((membership) => membership.organization);
}

getUsersMembersOrganizations を呼び出すことにより、findPartnerConfigurationDetails は、communityOrganizationRepository でフィルター検索を実行するために必要な組織データを取得し、関連する構成のみが返されるようにします。

Clerk と Novu の間で同期する必要があるのはユーザーと組織のみです。組織のメンバーを同期する必要はありません。


ユーザーと組織の同期

データベース ID を同期するには 2 つの方法があります:

  • middleware - any endpoint in API will sync the IDs if it detects that JWT doesn’t yet contain an internal ID.
  • webhook - as soon as the user/org is registered in Clerk, Clerk calls Novu’s API webhook, and we sync it.

Moving User Management from In-House to a Product: Why We Did It and What We Learned

Here is the flow we had in mind:

  1. A user creates a new account via frontend using the Clerk component
  2. Gets a new JWT containing Clerk user-id
  3. Any request that hits the API triggers the syncing process (given it hasn’t yet happened)
  4. A new user is created in Novu’s MongoDB containing the Clerk’s externalId
  5. Clerk user object gets updated with Novu internal object id (saved as externalId in Clerk)
  6. The new token returned from Clerk now contains an externalId that is equal to Novu's internal user ID.
  7. In the Clerk strategy in validate() function on API - we set _id to equal to externalId so it is compatible with the rest of the app.

Note
In the application, we always expect Novu’s internal id on input and we always return internal id on output - its important for the application to work as is without major changes to the rest of the code.
API expects internal _id everywhere and it needs to be MongoDB ObjectID type, because it parses this user id back to ObjectID e.g. when creating new environment or any other entity which needs reference to user.

The same logic applies to organizations; just the endpoint is different.


What is stored in Clerk vs Novu

Users

For the users, we store everything in Clerk. All the properties are mostly just simple key/value pairs and we don’t need any advanced filtering on them, therefore they can be retrieved and updated directly in Clerk.

In internal MongoDB, we store just the user internal and external ids.

The original Novu user properties are stored in Clerk’s publicMetadata :

export type UserPublicMetadata = {
  profilePicture?: string | null;
  showOnBoardingTour?: number;
};

There are also many other attributes coming from Clerk which can be set on the user.

Organizations

For the organizations, we store everything in Clerk except for apiServiceLevel, partnerConfigurations, and branding since they are “native” to Clerk and we update those attributes directly there via frontend components and so we don’t need to sync with our internal DB after we change organization name or logo via Clerk component.

Moving User Management from In-House to a Product: Why We Did It and What We Learned


Injection of Enterprise Edition providers

The goal here was to replace the community (open source) implementation with Clerk while being minimally invasive to the application and to keep the Clerk implementation in a separate package.

This means we need to keep the changed providers (OrganizationRepository, AuthService…) on the same place with the same name so we don’t break the imports all over the place, but we need to change their body to be different based on feature flag.

The other option would be to change all of these providers in the 100+ of files and then import the EE(enterprise edition) package everywhere, which is probably not a good idea.

This turned out to be quite challenging due to the fact that users, organization and members are relatively deeply integrated to the application itself, referenced in a lot of places and they’re also tied to MongoDB specifics such as ObjectID or queries (create, update, findOne …).

The idea is to provide different implementation using NestJS dynamic custom providers where we are able to inject different class/service on compile time based on the enterprise feature flag.

This is the most promising solution we found while keeping the rest of the app mostly untouched, there are some drawbacks explained later.


AuthService & AuthModule - dynamic injection

Moving User Management from In-House to a Product: Why We Did It and What We Learned

We have two implementations of AuthService - community and enterprise one (in private package), we inject one of those as AUTH_SERVICE provider.

We need to however have a common interface for both IAuthService

Since we also need to change the AuthModule, we initialize two different modules based on the feature flag like this:

function getModuleConfig(): ModuleMetadata {
  if (process.env.NOVU_ENTERPRISE === 'true') {
    return getEEModuleConfig();
  } else {
    return getCommunityAuthModuleConfig();
  }
}

@Global()
@Module(getModuleConfig())
export class AuthModule {
  public configure(consumer: MiddlewareConsumer) {
    if (process.env.NOVU_ENTERPRISE !== 'true') {
      configure(consumer);
    }
  }
}

The reason why the EEModule can be a standalone module in the @novu/ee-auth package which we would just import instead of the original AuthModule and instead we are initializing one module conditionally inside API, is that we are reusing some original providers in the EE one - e.g. ApiKeyStrategy , RolesGuard, EnvironmentGuard etc which resides directly in API.

We would need to import them in the @novu/ee-auth package which would require to export these things somewhere (probably in some shared package) and it introduces other issues like circular deps etc - it can be however refactored later.

Repositories - users, organizations, members

Same logic applies for the repositories. No module is being initialized here, they’re just directly injected to the original repository classes.

Moving User Management from In-House to a Product: Why We Did It and What We Learned


Controllers

The controllers are being conditionally imported from inside @novu/api . The reason for that is the same as in the auth module, there are too many imports that the controllers uses, that we would either need to move to @novu/ee-auth or move them to a separate shared package - which would then trigger a much bigger change to the other unrelated parts of the app, which would increase the scope of this change.

function getControllers() {
  if (process.env.NOVU_ENTERPRISE === 'true') {
    return [EEOrganizationController];
  }

  return [OrganizationController];
}

@Module({
  controllers: [...getControllers()],
})
export class OrganizationModule implements NestModule {
    ...
}


Issues with this approach

The main issue here is the need for common interface for both of the classes - community and enterprise. You want to remain compatible in both community and enterprise versions, so when there is a this.organizationService.getOrganizations() method being called in 50 places in the app - you need an enterprise equivalent with the same name otherwise you need to change 50 places to call something else.

This results in not-so-strict typing and methods without implementation

Moving User Management from In-House to a Product: Why We Did It and What We Learned

We need to have a common interface for both, however the community one relies on MongoDB methods and needs different method arguments as the enterprise one which causes a use of any to forcefully fit both classes etc.
In some cases we don’t need the method at all, so we need to throw Not Implemented .


Endpoints modification

We modified the endpoints as follows:

  • AuthController: Removed and replaced by frontend calls to Clerk.
  • UserController: Removed, added a sync endpoint for Clerk users with MongoDB.
  • OrganizationController: Removed several endpoints, which can be migrated later.
  • InvitesController: Completely removed.
  • StorageModule: Completely removed.

Key points to consider and avoid

  1. Avoid Storing Frequently Changing Properties in JWT
    • Example: environmentID
    • It can be cumbersome to update these properties.
  2. Simplify Stored Data Structures
    • Avoid storing complex structures in user, organization, or member records.
    • Clerk performs optimally with simple key:value pairs, not arrays of objects.
  3. Implement a User/Organization Replication Mechanism
    • This helps bridge the gap during the migration period before Clerk is fully enabled.
    • Use MongoDB triggers to replicate newly created users and organizations to both Clerk and your internal database.
  4. Store Original Emails
    • Do not sanitize emails as Clerk uses the original email as a unique identifier.

Team Spotlight

Lead Engineer: Adam Chmara

Platform Team Lead: Denis Kralj


Summary

Our implementation approach comes to the fact that we offloaded the Users, Organizations and Members management to Clerk.

The data property injection to Novu’s Controllers (endpoints) layer, Business layer and data layer happens based on “Enterprise” feature flag validation.

We are leveraging pre-built Clerk components on the frontend and reducing the need to build and maintain our own custom implementation on the backend.

You can also observe below the diagram of the current state after implementing Clerk.

Moving User Management from In-House to a Product: Why We Did It and What We Learned


後知恵ボーナスポイント

ユーザー管理に Clerk を実装することを決定したとき、Clerk が将来サポートおよび提供する機能を活用することによる長期的なメリットもオプトインしました。

近い将来サポートを検討する可能性のあるものの例をいくつか示します:

  • きめ細かいアクセス制御 (FGAC)
    • 属性ベース: FGAC は多くの場合、属性ベースのアクセス制御 (ABAC) を使用して実装され、アクセスの決定はユーザー、リソース、環境のさまざまな属性に基づいて行われます。属性には、ユーザーの役割、部門、リソースの種類、時刻などが含まれます。
    • 柔軟性: FGAC は、詳細な条件ベースのアクセス制御を可能にすることで、より優れた柔軟性と粒度を提供します。これは、非常に特殊なシナリオに合わせて権限を微調整できることを意味します。
    • 動的: FGAC は、時間に敏感なアクセスや位置ベースの制限など、環境の変化に動的に適応できます。
    • 詳細な権限: FGAC の権限はより具体的であり、個々のアクション、ユーザー、または状況に合わせて調整できます。

Nov にこのレベルの詳細な柔軟性を提供することは、実装が潜在的に複雑になるため、範囲外であるか、最初から削られていた可能性があります。

  • ユーザーのなりすまし

    当社の顧客サクセスチームまたはサポートチームはこれを使用して、パスワードやその他の認証の詳細を知る必要がなく、偽装ユーザーの観点から問題のトラブルシューティング、サポートの提供、または機能のテストを行うことができます。

    • ユーザーの問題の診断と解決にかかる時間と複雑さを軽減します。
    • サポート スタッフはユーザーとまったく同じようにシステムを表示し操作できるため、実行されるサポートまたは管理アクションが正確であることが保証されます。
    • より迅速かつ効果的なサポートを提供することで、ユーザーの満足度を高めます。

簡単に言うと、現在は認証インフラストラクチャが用意されているため、Nov ユーザーのエクスペリエンスを簡単に改善できるようになります。


AuthN (またはその他) に関連する追加機能を提案したい場合は、ロードマップにアクセスしてリクエストを確認して賛成票を投じるか、アイデアを送信してください。


読んだ内容は気に入りましたか?さらに最新情報を確認するには「フォロー」をクリックし、下にコメントをドロップしてください。 ❤️あなたの意見を聞きたいです?

Moving User Management from In-House to a Product: Why We Did It and What We Learned

エミール・ピアース

コーヒーショップでコードとワードを書いています。

以上がユーザー管理を社内から製品に移行: なぜそれを行ったのか、そして何を学んだのかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。