# 使用自定义按钮登录

#### 场景介绍

应用应遵照【华为账号登录】按钮使用规则，在登录页面嵌入自定义华为账号登录按钮。当用户点击该按钮后，应用调用华为账号登录API获取Authorization Code，并通过服务端交互获取用户的UnionID、OpenID以完成登录。

#### 约束与限制

自定义按钮登录能力支持Phone、Tablet、PC/2in1设备。并且从5.1.0(18)版本开始，新增支持Wearable设备；从5.1.1(19)版本开始，新增支持TV设备；从26.0.0版本开始，新增支持Car设备。

#### 业务流程

流程说明：

1.  展示自定义按钮调用登录API阶段（序号1-4）：
    
    1.  用户打开应用进行登录，点击自定义登录按钮，应用传forceLogin等参数后调用华为账号登录API，请求华为账号授权信息。
    2.  如华为账号未登录，将拉起华为账号登录页，用户登录后，将返回Authorization Code等数据给应用。
    3.  如华为账号已登录，将直接返回Authorization Code等数据给应用。
2.  用户关联应用账号阶段（序号5-16）：
    
    1.  应用服务端通过Authorization Code获取到Access Token，再使用Access Token调用解析凭证接口获取用户相关信息。通过Authorization Code凭证获取用户信息可以有效避免黑客通过数据遍历、身份伪造、重放攻击等手段导致的安全风险。
    2.  应用服务端将业务登录凭证SessionId、UnionID/OpenID传给应用，应用获取到UnionID/OpenID可用于判断华为账号是否登录等功能。
    3.  应用对用户身份标识UnionID/OpenID、业务登录凭证SessionId信息进行认证后，通过UnionID/OpenID判断用户是否已关联应用系统数据库，如已关联，则完成用户登录；如未关联，则创建新用户，绑定UnionID/OpenID。

#### 接口说明

使用API获取UnionID登录关键接口如下表所示，具体API说明详见API参考。

| 接口名 | 描述 |
| --- | --- |
| createLoginWithHuaweiIDRequest(): LoginWithHuaweiIDRequest | 创建账号登录请求。LoginWithHuaweiIDRequest中的forceLogin参数用来控制当用户未登录华为账号时，是否强制拉起华为账号登录界面。 |
| constructor(context?: common.Context) | 创建登录请求Controller。 |
| executeRequest(request: AuthenticationRequest): Promise<AuthenticationResponse> | 通过Promise方式执行登录操作。 |

上述接口需在页面或自定义组件生命周期内调用。

#### 开发前提

在进行代码开发前，请确保已按照“开发准备”章节中的指导完成配置签名和指纹、配置Client ID。此场景无需申请账号权限。

#### 客户端开发

1.  根据【华为账号登录】按钮规范开发自定义登录图标按钮，参考如下步骤在点击事件中完成华为账号登录API调用。
    
2.  导入authentication模块及相关公共模块。
    
    ```
    import { authentication } from '@kit.AccountKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import { util } from '@kit.ArkTS';
    import { BusinessError } from '@kit.BasicServicesKit';
    ```
    
3.  创建登录请求并设置参数。
    
    ```
    // 创建登录请求，并设置参数
    const loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
    // 用户是否需要登录授权，该值为true且用户未登录或未授权时，会拉起用户登录或授权页面
    loginRequest.forceLogin = true;
    // 建议使用generateRandomUUID生成state，可用于一致性比对，防止跨站攻击
    loginRequest.state = util.generateRandomUUID();
    ```
    
4.  调用AuthenticationController对象的executeRequest方法执行登录请求，并处理登录结果，获取到Authorization Code及ID Token。之后将Authorization Code传给应用服务端处理，可参考客户端与服务端交互开发的开发步骤a和b。通过code凭证获取用户信息可以有效避免黑客通过数据遍历、身份伪造、重放攻击等手段导致的安全风险。应用可以通过公开的网址获取到华为账号服务器发布的公钥，对签名和ID Token中的必要信息进行验证，以证明其没有被篡改过。解析ID Token可参考ID Token解析与验证。
    
    ```
    // 执行登录请求
    try {
      // 此示例为代码片段，实际需在自定义组件实例中使用，并传入有效的Context上下文对象
      const controller = new authentication.AuthenticationController(this.getUIContext().getHostContext());
      controller.executeRequest(loginRequest).then((response: authentication.LoginWithHuaweiIDResponse) => {
        const loginWithHuaweiIDResponse = response as authentication.LoginWithHuaweiIDResponse;
        const state = loginWithHuaweiIDResponse.state;
        if (state && loginRequest.state !== state) {
          hilog.error(0x0000, 'testTag', `Failed to login. The state is different, response state: ${state}`);
          return;
        }
        hilog.info(0x0000, 'testTag', 'Succeeded in logging in.');
        const loginWithHuaweiIDCredential = loginWithHuaweiIDResponse?.data;
        const authorizationCode = loginWithHuaweiIDCredential?.authorizationCode;
        // 开发者处理authorizationCode
      }).catch((error: BusinessError) => {
        dealAllError(error);
      });
    } catch (error) {
      dealAllError(error);
    }
    ```
    
    ```
    // 错误处理
    function dealAllError(error: BusinessError): void {
      hilog.error(0x0000, 'testTag', `Failed to login. Code: ${error.code}, message: ${error.message}`);
      // 在应用登录涉及UI交互场景下，建议按照如下错误码指导提示用户
      if (error.code === ErrorCode.ERROR_CODE_LOGIN_OUT) {
        // 用户未登录华为账号，请登录华为账号并重试或者尝试使用其他方式登录
      } else if (error.code === ErrorCode.ERROR_CODE_NETWORK_ERROR) {
        // 网络错误，请检查当前网络状态并重试
      } else if (error.code === ErrorCode.ERROR_CODE_INTERNAL_ERROR) {
        // 登录失败，请尝试使用其他方式登录
      } else if (error.code === ErrorCode.ERROR_CODE_USER_CANCEL) {
        // 用户取消授权
      } else if (error.code === ErrorCode.ERROR_CODE_SYSTEM_SERVICE) {
        // 系统服务异常，请稍后重试或者尝试使用其他方式登录
      } else if (error.code === ErrorCode.ERROR_CODE_REQUEST_REFUSE) {
        // 重复请求，应用无需处理
      } else {
        // 应用登录失败，请尝试使用其他方式登录
      }
    }
    
    export enum ErrorCode {
      // 账号未登录
      ERROR_CODE_LOGIN_OUT = 1001502001,
      // 网络错误
      ERROR_CODE_NETWORK_ERROR = 1001502005,
      // 内部错误
      ERROR_CODE_INTERNAL_ERROR = 1001502009,
      // 用户取消授权
      ERROR_CODE_USER_CANCEL = 1001502012,
      // 系统服务异常
      ERROR_CODE_SYSTEM_SERVICE = 12300001,
      // 重复请求
      ERROR_CODE_REQUEST_REFUSE = 1001500002
    }
    ```
    

#### 服务端开发

1.  应用服务端使用Client ID、Client Secret、Authorization Code调用获取用户级凭证接口向华为账号服务器请求获取Access Token、Refresh Token。
    
2.  使用Access Token调用解析凭证接口获取用户的UnionID。
    
    **Access Token过期处理**
    
    由于Access Token的有效期仅为60分钟，当Access Token失效或者即将失效时（可通过REST API错误码判断），可以使用Refresh Token（有效期180天）通过刷新用户级凭证接口向华为账号服务器请求获取新的Access Token。
    
    1.  当Access Token失效时，若应用不使用Refresh Token向华为账号服务器请求获取新的Access Token，账号的授权信息将会失效，导致使用Access Token的功能都会失败。
        
    2.  当Access Token非正常失效（如修改密码、退出账号、删除设备）时，应用可重新登录授权获取Authorization Code，向华为账号服务器请求获取新的Access Token。
        
    
    **Refresh Token过期处理**
    
    由于Refresh Token的有效期为180天，当Refresh Token失效后（可通过REST API错误码判断），应用服务端需要通知客户端，重新调用授权接口，请求用户重新授权。
    
3.  应用在自己的用户体系通过查询获取的UnionID判断该用户是否已关联。如已关联，则完成用户登录；如未关联，则创建新用户，绑定UnionID，完成用户登录。