{{announcement.body}}
{{announcement.title}}

Account Binding for Card Ability Part 1

DZone 's Guide to

Account Binding for Card Ability Part 1

In this article, I’m going to show you how to perform the Account Binding process from points 1 to 4.

· Integration Zone ·
Free Resource

Card abilities allow send Push events to your users, so instead of displaying a notification, your card will be updated with the data sent by the event. Before sending a push event to a user you need to know his OpenId (something similar to push notifications), the Open Id is generated by Huawei and associates a user with an Ability. To know the OpenId of a user you must perform a process called Account Binding.

Account Binding Process

The server receives 2 requests:

  1. From Huawei: To start the Account Binding process and obtain the login page

  2. From the app or login page: To receive the user's login credentials and bind his account Id to his Open id

In this article, I’m going to show you how to perform the Account Binding process from points 1 to 4.

Previous Requirements

  • An enterprise developer account
  • A Card Ability
  • The Huawei Ability Test Tool
  • Your own server (For this example will be used AWS)

Ability Gallery Console Configuration

Go to your console, choose your card ability and then go to Account authorization

Enable the custom sign-in and provide the URL which will receive the request, an access key (AK) and a secret key (SK). The AK and SK can contain up to 64 characters excluding the space. Huawei will use the secret Key to create a signature with the request timestamp using the HMAC algorithm. You must use the SK to validate the signature from your server side to prevent atacks.

Preparing the Server

When a user requires to link his account to your ability, Huawei will send a post request to your server with the next format:

Headers

Java
 




x


 
1
'Accept-Encoding': 'gzip',
2
'content-type': 'application/json; utf-8',
3
sign: 'A/rD2kV2GT08F+K7Q2WnvZL0jQoxISbQWhlo8GhtRHc=',
4
 ts: '1591977828057'



Body

Java
 




xxxxxxxxxx
1
40


 
1
{
2
 
          
3
    "version""1.0",
4
 
          
5
    "header": {
6
 
          
7
        "type""Directive",
8
 
          
9
        "timestamp""1591977828057",
10
 
          
11
        "name""AcceptGrant",
12
 
          
13
        "namespace""Authorization"
14
 
          
15
    },
16
 
          
17
    "inquire": {
18
 
          
19
        "inquireId""efd1f6ca-8fdf-11e8-9eb6-529269fb1459",
20
 
          
21
        "payload": {
22
 
          
23
            "grant": {
24
 
          
25
                "type""OAuth2.Authorization",
26
 
          
27
                "openId""the user’s openId",
28
 
          
29
                "sign""A/rD2kV2GT08F+K7Q2WnvZL0jQoxISbQWhlo8GhtRHc=",
30
 
          
31
                "abilityId""7a0af511a91f4591b4efbbaacd8bee60"
32
 
          
33
            }
34
 
          
35
        }
36
 
          
37
    }
38
 
          
39
}
40
 
          



A user can bind or unbind his account at any time, the body.header.namespase will indicate the request type. You must provide an authentication method in the response, it could be a link to your login webpage or a deeplink to the login page of your app. In this case we are going to use the app.

Prepare your server to receive the request and return an auth method with the user’s open Id. For security reasons is highly recommended return an anonymized Open Id and save both Ids in your database.

Java
 




x


1
'use strict'
2
const AWS = require('aws-sdk');
3
 
          
4
AWS.config.update({ region"us-east-2" });
5
exports.handler = async(event, callback) => {
6
 
          
7
    // Catches the request from AGC and returns a link to a login page
8
 
          
9
  
10
 
          
11
    const ddb = new AWS.DynamoDB({ apiVersion"2012-08-10" });
12
 
          
13
    const documentClient = new AWS.DynamoDB.DocumentClient({ region"us-east-2" });
14
 
          
15
    var deeplink = "scheme://dummyapp.com/login?openId=";
16
 
          
17
    var headers = event.headers;
18
 
          
19
    console.log(event);
20
 
          
21
    console.log(headers);
22
 
          
23
   
24
 
          
25
    const response = defaultResponse;
26
 
          
27
    if (validateHeaders(headers)) {
28
 
          
29
        var body = event.body;
30
 
          
31
        console.log(JSON.stringify(body));
32
 
          
33
        const namespace = body.header.namespace;
34
 
          
35
        const openId = body.inquire.payload.grant.openId;
36
 
          
37
        const anonOpenId = getAnonOpenId(openId);
38
 
          
39
        
40
 
          
41
        if (namespace === "Authorization") {
42
 
          
43
            
44
 
          
45
            //Store the open and anonimyzed openId
46
 
          
47
            try {
48
 
          
49
                const data = await saveIds(openId, anonOpenId,documentClient);
50
 
          
51
                console.log(data);
52
 
          
53
            }
54
 
          
55
            catch (err) {
56
 
          
57
                console.log(err);
58
 
          
59
            }
60
 
          
61
            //Target a login activity in your app
62
 
          
63
            const deepLink = deeplink + anonOpenId;
64
 
          
65
            response.reply.accountLoginAddr.deepLink.url = deepLink;
66
 
          
67
            return response;
68
 
          
69
        }
70
 
          
71
  
72
 
          
73
        if (namespace === "Deauthorization") {
74
 
          
75
            removeIds(anonOpenId);
76
 
          
77
            //sendAccountUnbindingNotification(openId)
78
 
          
79
            return response;
80
 
          
81
        }
82
 
          
83
    }
84
 
          
85
    response.errorCode = "authFailed";
86
 
          
87
    response.errorMessage = "Invalid sign";
88
 
          
89
    return response;
90
 
          
91
};
92
 
          
93
  
94
 
          
95
function validateHeaders(headers) {
96
 
          
97
    //To prevent replay attacks, the server must verify that the absolute
98
 
          
99
    //value of the difference between the timestamp specified by ts in the
100
 
          
101
    //request and the current time is within a specified range.
102
 
          
103
    const ts = headers.ts;
104
 
          
105
    var hrTime = Date.now();
106
 
          
107
    const validPeriod = 5 * 60 * 1000;
108
 
          
109
    const difference = Math.abs(hrTime - ts);
110
 
          
111
    if (difference > validPeriod) {
112
 
          
113
        return false;
114
 
          
115
    }
116
 
          
117
    //Parameter signature: Base64(HMAC-SHA256(secretKey, ts)).
118
 
          
119
    //In the signature, SecretKey indicates the secret access key that you
120
 
          
121
    //enter in HUAWEI Ability Gallery.
122
 
          
123
    var crypto = require('crypto');
124
 
          
125
    var hmac = crypto.createHmac("sha256", process.env.secret);
126
 
          
127
    hmac.update(ts);
128
 
          
129
    var hash = hmac.digest('base64');
130
 
          
131
    const sign = headers.sign;
132
 
          
133
    console.log("signature", sign);
134
 
          
135
    console.log("local", hash);
136
 
          
137
  
138
 
          
139
    if (sign === hash) {
140
 
          
141
        return true;
142
 
          
143
    }
144
 
          
145
    return false;
146
 
          
147
}
148
 
          
149
  
150
 
          
151
function getAnonOpenId(openId) {
152
 
          
153
    const crypto = require('crypto');
154
 
          
155
    const hash = crypto.createHash('sha256');
156
 
          
157
    hash.update(openId);
158
 
          
159
    const anonOpenId = hash.digest('hex');
160
 
          
161
    console.log("AnonOpenId", anonOpenId);
162
 
          
163
    return anonOpenId;
164
 
          
165
}
166
 
          
167
  
168
 
          
169
const defaultResponse = {
170
 
          
171
        "version""1.0",
172
 
          
173
        "reply": {
174
 
          
175
            "accountLoginAddr": {
176
 
          
177
                "webURL""https://hag.huawei.com/hagLogin.html?openId=",
178
 
          
179
                "deepLink": {
180
 
          
181
                    "url""",
182
 
          
183
                    "appName""DummyApplication",
184
 
          
185
                    "appPackage""com.hms.example.dummyapplication",
186
 
          
187
                    "minVersion": 1.1,
188
 
          
189
                    "minAndroidAPILevel": 27
190
 
          
191
                },
192
 
          
193
                "quickApp"""
194
 
          
195
            }
196
 
          
197
        },
198
 
          
199
        "errorCode""0",
200
 
          
201
        "errorMessage""ok"
202
 
          
203
    };
204
 
          
205
  
206
 
          
207
function saveIds(openId, anonOpenId, documentClient) {
208
 
          
209
    // body...
210
 
          
211
    //Connect to dynamo db and save the Ids
212
 
          
213
  
214
 
          
215
    console.log("Saving ids");
216
 
          
217
    //Store the ids
218
 
          
219
    const ids = {
220
 
          
221
        TableName"HuaweiId",
222
 
          
223
        Item: {
224
 
          
225
            openId: openId,
226
 
          
227
            anonOpenId: anonOpenId
228
 
          
229
        }
230
 
          
231
    };
232
 
          
233
    console.log(JSON.stringify(ids));
234
 
          
235
    return documentClient.put(ids).promise();
236
 
          
237
}
238
 
          
239
  
240
 
          
241
function removeIds(anonOpenId) {
242
 
          
243
    // body...
244
 
          
245
    //Connect to dynamo db and remove the Ids, then send an account unbind result notification
246
 
          
247
}
248
 
          
249
 
          
250
 
          



Preparing the App

Edit your Android Manifest to create a deeplink for your login activity (you can also use theAnroid Studio's applinks assistant)

Java
 




xxxxxxxxxx
1
29


 
1
<activity
2
 
          
3
     android:name=".activity.LoginActivity"
4
 
          
5
     android:label="LogIn">
6
 
          
7
     <intent-filter>
8
 
          
9
         <action android:name="android.intent.action.VIEW" />
10
 
          
11
  
12
 
          
13
         <category android:name="android.intent.category.DEFAULT" />
14
 
          
15
         <category android:name="android.intent.category.BROWSABLE" />
16
 
          
17
  
18
 
          
19
         <data
20
 
          
21
             android:host="dummyapp.com"
22
 
          
23
             android:path="/login"
24
 
          
25
             android:scheme="scheme" />
26
 
          
27
     </intent-filter>
28
 
          
29
     <intent-filter>



NOTE: The scheme can be your app name, packagename, or whaever you want except HTTP or HTTPS

At the OnCreate method of your login Activity, check if the Activity was called from a deep link and look for the anonymized Open Id

Java
 




xxxxxxxxxx
1
21


 
1
val appLinkIntent = getIntent()
2
 
          
3
 val appLinkData = appLinkIntent.getData()
4
 
          
5
 if (appLinkData != null) {
6
 
          
7
     val openId = appLinkData.getQueryParameter("openId")
8
 
          
9
     if (openId != null && openId != "") {
10
 
          
11
         //If the user is not signed in the app perform the login
12
 
          
13
         //If the user is already signed, display a confirmation dialog to continue with his account
14
 
          
15
         }
16
 
          
17
  
18
 
          
19
     }
20
 
          
21
 }



Conclusion

Now you are ready to receive the Account Binding request in your server and handling it from your app, the next step is linking the User Id with his open Id and send the Account Binding/Unbind result notification.

Stay tuned for the next part!

Topics:
android, application, integration, mobile, opensource, tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}