Skip to main content

Registration Flow

There are 3 major steps that your application should implement in order to have the registration flow completely implemented using RTAS:

  • Create Token (application-server) - Creates a RTAS token for the front-end to listen to events, and sends the registration created event.
  • Listen RTAS Events (front-end) - Front-end implementation for each RTAS event
  • Handle Callback (application-server) - Handles and sends registration events to the front-end user

There are two events for the RTAS registration flow.

Create Token

The application server calls RTAS using the RTAS OpenAPI client to get an authentication token. This authentication token is then forwarded to the front-end module alongside with the address to wich the front end connects to the RTAS.

To create the registration token, the TF API returns an unique_id for the registration on the RegisterCode request. This unique_id will replace the jwt timestamp on the previou RTAS version, simplifying the rntire registration implementation. Because of this unique_id there is no need to store anything on the server side, because the unique_id of the registration is returned on the TF callback.

After the registration token is created, the application server should notify the RTAS Server that a new registration was created.

Registration Created event

This event serves to provide some information to the frontend about the new registration, such as the user ID, username and other properties for a simpler implementation of the UI.

The OpenAPI client provided only has one API for the registration callback. This means that there are properties that are shared between the events and properties that are unique to the event. These are the properties of the registration created event:

PropertyIs Required
StatusYes
Application NameYes
User IDYes
User display nameYes
QR CodeYes
QR Code expirationYes
Deeplink URLYes

Example Implementation

An application server implementation would look like:

package main

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"

tfsdkclient "securityside.com/tf/go-sdk"
tfsdkrequests "securityside.com/tf/go-sdk/requests"
rtas "securityside.com/rtas/go-sdk"
)

const (
RTASFrontEndURL = "" // Load from config file. This value should be provided by SecuritySide team.
ApplicationName = "Your application" // Value representing the application name.
RTASAddress = "" // Load from config file. This value should be provided by SecuritySide team.
ClientID = "" // Load from config file. This value should be provided by SecuritySide team.
ClientSecret = "" // Load from config file. This value should be provided by SecuritySide team.
)

var (
RTASClient, _ = initializeRTAS(RTASAddress, ClientID, ClientSecret)
)

func initializeRTAS(rtasAddress, clientID, clientSecret string) (*rtas.APIClient, error) {
rtasCnf := rtas.NewConfiguration()
rtasCnf.Servers = rtas.ServerConfigurations{{URL: rtasAddress}}
apiClient := rtas.NewAPIClient(rtasCnf)

token, resp, err := apiClient.LoginApi.LoginExecute(apiClient.LoginApi.Login(context.Background()).Body(*rtas.NewLoginRequest(clientID, clientSecret)))
if err != nil {
return nil, err
}
defer resp.Body.Close()

rtasCnf.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token.Token))
go tokenRenew(apiClient, 30*time.Minute) // The token renews every 30 minutes. This token is used in order to authenticate the application server with the RTAS application.

return apiClient, nil
}

func tokenRenew(apiClient *rtas.APIClient, timerInterval time.Duration) {
ticker := time.NewTicker(timerInterval)
for range ticker.C {
token, resp, err := apiClient.TokenRenewApi.RenewTokenExecute(apiClient.TokenRenewApi.RenewToken(context.Background()))
if err != nil {
panic(err.Error())
}
_ = resp.Body.Close()

apiClient.GetConfig().AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token.Token))
}
}

type registrationFlowResponse struct {
QRCode string `json:"qr_code"`
DeepLinkURL string `json:"deep_link_url"`
RTASToken string `json:"rtas_token"`
RTASURL string `json:"rtas_url"`
}

func registrationFlowController(w http.ResponseWriter, r *http.Request) {
const (
userID = "john.doe"
userName = "John Doe"
)

// Call TF SDK to create a registration code for the user
registerResponse := tfsdkrequests.RegisterCodeV2Response{}
// Initialize Client with a ClientBuilder struct
client := tfsdkclient.Client{}
qrCodeByte, _ := client.GetRegistrationQRCode(context.Background(), registerResponse, 200)
qrCode := string(qrCodeByte)
deeplinkURL, _ := client.GenerateRegistrationDeeplinkURL(registerResponse)

rtasToken, err := rtasCreateRegistrationFlowToken(r.Context(), userID, registerResponse.UniqueID)
if err != nil {
panic(err)
}

// Send registration created RTAS event here
if err := rtasSendRegistrationCreatedCallback(r.Context(), userID, userName, qrCode, deeplinkURL, registerResponse.UniqueID, registerResponse.ExpiresAt); err != nil {
panic(err)
}

resBytes, err := json.Marshal(registrationFlowResponse{
QRCode: qrCode,
DeepLinkURL: deeplinkURL,
RTASToken: rtasToken,
RTASURL: RTASFrontEndURL,
})
if err != nil {
panic(err)
}

w.WriteHeader(http.StatusOK)
if _, err := w.Write(resBytes); err != nil {
panic(err)
}
}

func rtasCreateRegistrationFlowToken(ctx context.Context, userID, registrationCodeUniqueID string) (string, error) {
token, resp, err := RTASClient.EndUserRegistrationTokenApi.RegistrationEndUserTokenV2Execute(RTASClient.EndUserRegistrationTokenApi.RegistrationEndUserTokenV2(ctx).Body(*rtas.NewEndUserRegistrationTokenRequestV2(userID, registrationCodeUniqueID)))
if err != nil {
return "", err
}
defer resp.Body.Close()

return token.Token, nil
}

func rtasSendRegistrationCreatedCallback(ctx context.Context, userID, userName, qrCode, deepLinkURL, registrationCodeUniqueID string, qrCodeExpiration int64) error {
body := *rtas.NewRegistrationCallbackRequestV3(registrationCodeUniqueID, rtas.REGISTRATION_CREATED_V2, userID)
body.SetUserDisplayName(userName)
body.SetQrCode(qrCode)
body.SetQrCodeExpiration(qrCodeExpiration)
body.SetApplicationName(ApplicationName)
body.SetDeeplinkUrl(deepLinkURL)

// signalr connection properties
body.SetCloseConnectionOnReceive(false)
body.SetStoreInCache(true)
body.SetCacheExpiresAfterMinutes(15)

resp, err := RTASClient.RegistrationCallbackApi.RegistrationCallbackV3Execute(RTASClient.RegistrationCallbackApi.RegistrationCallbackV3(ctx).Body(body))
if err != nil {
var e rtas.GenericOpenAPIError
if errors.As(err, &e) {
fmt.Printf("RTAS Error\n%s\n%s\n", e.Error(), string(e.Body()))
return err
}
fmt.Printf("RTAS Error\n%s\n", err.Error())
return err
}
defer resp.Body.Close()

return nil
}

Listen RTAS Events

In order to listen for RTAS Events the end-user should receive a RTAS Token and Endpoint URL from the application server so it can initialize the TF-Realtime module.

Web

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"></meta>
<meta http-equiv="X-UA-Compatible" content="IE=edge"></meta>
<meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>
<title>TF-Realtime - Registration</title>
</head>
<body>
<div class="rtas"></div>

<script src="https://cdn.securityside.com/packages/realtime-auth/tf-realtime/9.0.2/index.js"></script>
<script>

// Make a request that initialize registration flow
fetch("https://your_backend_url.com/tf/create-qr", { method: "POST" })
.then((response) => response.json())
.then((responseData) => {

// responseData: {
// "rtas_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJncm91cHNpZCI6ImFwcDp0Zi1kZXYtYmFja29mZmljZToyZmYyNjU4NC1mYWYxLTQwYmQtODg4My0xY2VhMjE1YTRlNWIsdXNlcjp0ZXN0ZV92YTQsdH...",
// "rtas_url": "https://signalr-ha.dev.securityside.com"
// }

new TFRealTime()
.setTargetElement("div.rtas")
.setUrl(responseData.rtas_url)
.setToken(responseData.rtas_token)
.setOnErrorCallback((errorData) => console.log("OnError -> ", errorData))
.setOnConnectionEstablishedCallback(() => console.log("-= OnConnectionEstablished =-"))
.setLanguage("PT_PT")
.setRegistrationType()
.setOnRegistrationPendingCallback((data) => {
console.log("OnRegistrationPending -> ", data);
// data: {
// "application_name": "Your application",
// "deeplink_url": "https://open.dev.trustfactor.securityside.com/register?payload=djIAAAAAAABkB...",
// "qr_code": "iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAIAAABEtEjdAAAfr0lEQVR4nOzdT6gkV/k38Lm372RmMiZxM...",
// "qr_code_expiration": 1678104748,
// "user_display_name": "John Doe",
// "user_id": "john.doe"
// }
})
.setOnRegistrationSuccessCallback((data) => {
console.log("OnRegistrationSuccess -> ", data);
// data: {
// "application_name": "Your Application",
// "contract_key": "7D/Iw9i3NdYLbHfw0UsVk3LcJo8PpQXoOPIRBhvF4Bk=",
// "device_info": {
// "device_model": "TFVA",
// "manufacturer": "SecuritySide",
// "marketing_name": "SecuritySide TFVA",
// "name": "Virtual Agent #1",
// "operating_system": "TFVA",
// "os_version": "1.0"
// },
// "device_key": "N+RlRWij82dHZ4TJ/slTbRP+UltX9SGC5DMhZOULbP4=",
// "user_display_name": "John Doe",
// "user_id": "john.doe"
// }
})
.setOnRegistrationExpiredCallback((data) => {
console.log("OnRegistrationExpired -> ", data);
// data: {
// "application_name": "Your Application",
// "user_display_name": "John Doe",
// "user_id": "john.doe"
// }
})
.build();

})
.catch((error) => {
console.error("Error:", error);
});

</script>
</body>
</html>

Mobile

In order to properly implement RTAS on a mobile application, the application server should expose an endpoint serving a static HTML page with the following content:

    <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="rtas"></div>
<script src="https://cdn.securityside.com/packages/realtime-auth/tf-realtime/9.0.2/index.js"></script>
</body>
</html>
package com.securityside.realtime;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

/**
* MainActivity is the main activity for the Android application.
* It uses a WebView to load and interact with web content that includes JavaScript code
* for real-time registration and transaction decisions. This activity also includes
* click listeners for two buttons: one for registration and one for decision-making.
*/
public class MainActivity extends AppCompatActivity {
// URL for RTAS endpoint
private static final String URL = "https://signalr.rtas.trustfactor.cloud";
// Authentication token
private static final String TOKEN = "YOUR_AUTH_TOKEN_HERE";

// TAG for logging
private static final String TAG = MainActivity.class.getSimpleName();

// UI elements
private WebView mWebView;
private Button mBtnRegistration;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setView(); // Initialize UI elements
setListeners(); // Set click listeners for buttons
}

/**
* Initializes the UI elements, including the WebView and buttons.
* Also, enables JavaScript in the WebView.
*/
@SuppressLint("SetJavaScriptEnabled")
private void setView() {
mWebView = findViewById(R.id.webView); // Initialize the WebView element
mBtnRegistration = findViewById(R.id.btnRegistration); // Initialize the Registration button
mWebView.getSettings().setJavaScriptEnabled(true); // Enable JavaScript in WebView
// This setting enables the use of DOM storage, which is typically needed for modern web applications. It allows websites to store data locally in the browser, improving performance.
mWebView.getSettings().setDomStorageEnabled(false);
// By setting this to false, you are disabling the ability for websites displayed in the WebView to access the device's file system. This can help prevent unauthorized access to sensitive files.
mWebView.getSettings().setAllowFileAccess(false);
// This setting disables web content from accessing the device's content provider. It restricts access to device content and enhances security.
mWebView.getSettings().setAllowContentAccess(false);
// When set to false, this setting prevents websites from accessing files via "file://" URLs. This is a good security measure to prevent web content from reading local files.
mWebView.getSettings().setAllowFileAccessFromFileURLs(false);
// Disabling universal access from file URLs ensures that web content cannot have unrestricted access to resources on the local file system. This setting is important for security.
mWebView.getSettings().setAllowUniversalAccessFromFileURLs(false);
}

/**
* Sets click listeners for the Registration and Decision buttons.
* When clicked, these buttons load different JavaScript interfaces and HTML content
* into the WebView for real-time authentication and transaction decisions.
*/
private void setListeners() {
// Set click listener for the Registration button
mBtnRegistration.setOnClickListener(v -> {
try {
// Add a JavaScript interface for Registration
mWebView.addJavascriptInterface(new WebAppInterfaceRegistration(MainActivity.this, URL, TOKEN), "WebAppInterfaceRegistration");
mWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// Add and execute JavaScript code for Registration
String javascriptCode =
"javascript:(function () {" +
" var url = WebAppInterfaceRegistration.getUrl();" +
" var token = WebAppInterfaceRegistration.getToken();" +
// JavaScript code for Registration
" new TFRealTime()\n" +
" .setTargetElement(\"div.se\")\n" +
" .setLanguage(\"PT_PT\")\n" +
" .setToken(token)\n" +
" .setUrl(url)\n" +
" .setOnErrorCallback((errorData) => {WebAppInterfaceRegistration.onError(JSON.stringify(errorData));})\n" +
" .setOnConnectionEstablishedCallback(() => {WebAppInterfaceRegistration.OnConnectionEstablished();})\n" +
" .setRegistrationType()\n" +
" .setOnRegistrationPendingCallback((data) => {WebAppInterfaceRegistration.onPending(JSON.stringify(data));})\n" +
" .setOnRegistrationExpiredCallback((data) => {WebAppInterfaceRegistration.onExpired(JSON.stringify(data));})\n" +
" .setOnRegistrationSuccessCallback((data) => {WebAppInterfaceRegistration.onSuccess(JSON.stringify(data));})\n" +
" .build();\n" +
" })();";
// Execute the JavaScript code using evaluateJavascript
mWebView.evaluateJavascript(javascriptCode, value -> Log.d("WebView", value));
}
});
// Load the registration HTML page from the assets folder
mWebView.loadUrl("https://your_backend_url.com");
} catch (Exception exception) {
Log.e(TAG, exception.getMessage());
}
});
}
}

The application should inject javascript after the document is loaded on the webview in order to properly initialize the RTAS client and create the WebSocket connection.

Handle Callback

Handle the TrustFactor registration callback.

Registration Success event

If the registration flow is completed (by registering a successfully TrustFactor contract), the application server should notify the RTAS Server using the callback received from TF to send a request to the RTAS server notifying the success of the operation.

The OpenAPI client provided only has one API for the registration callback. This means that there are properties that are shared between the events and properties that are unique to the event. These are the properties of the registration created event:

PropertyIs Required
StatusYes
Application NameYes
User IDYes
User display nameYes
Contract keyYes
Device InfoYes
Device KeyYes
Wrong Device TokenNo
Custom DataNo

Wrong Device Token is short-lived token (ie: 2 minutes duration) that the application-server transmits to the front-end using the RTAS so the user can remove the newly registered device. This might be useful if the user registers the wrong device or someone else reads the QR Code from the screen of the legitimate user. The application server should have an endpoint validating this token that calls TF SDK ResetContract API.

Example Implementation

In order to notify the end-user about the success of the registration flow, your code should look like:

package main

import (
"context"
"errors"
"fmt"
"net/http"
"time"

tfsdkcallback "securityside.com/tf/go-sdk/callback"
tfsdk "securityside.com/tf/go-sdk/models"
tfsdkcrypto "securityside.com/tf/go-sdk/util/crypto"
rtas "securityside.com/rtas/g-sdk/v4"
)

const (
RTASAddress = "" // Load from config file. This value should be provided by SecuritySide team.
ApplicationName = "Your application" // Value representing the application name.
ClientID = "" // Load from config file. This value should be provided by SecuritySide team.
ClientSecret = "" // Load from config file. This value should be provided by SecuritySide team.
)

var (
RTASClient, _ = initializeRTAS(RTASAddress, ClientID, ClientSecret)
)

func initializeRTAS(rtasAddress, clientID, clientSecret string) (*rtas.APIClient, error) {
rtasCnf := rtas.NewConfiguration()
rtasCnf.Servers = rtas.ServerConfigurations{{URL: rtasAddress}}
apiClient := rtas.NewAPIClient(rtasCnf)

token, resp, err := apiClient.LoginApi.LoginExecute(apiClient.LoginApi.Login(context.Background()).Body(*rtas.NewLoginRequest(clientID, clientSecret)))
if err != nil {
return nil, err
}
defer resp.Body.Close()

rtasCnf.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token.Token))
go tokenRenew(apiClient, 30*time.Minute) // The token renews every 30 minutes. This token is used in order to authenticate the application server with the RTAS application.

return apiClient, nil
}

func tokenRenew(apiClient *rtas.APIClient, timerInterval time.Duration) {
ticker := time.NewTicker(timerInterval)
for range ticker.C {
token, resp, err := apiClient.TokenRenewApi.RenewTokenExecute(apiClient.TokenRenewApi.RenewToken(context.Background()))
if err != nil {
panic(err.Error())
}
_ = resp.Body.Close()

apiClient.GetConfig().AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token.Token))
}
}

func tfRegisterCallbackController(w http.ResponseWriter, r *http.Request) error {
// Handle TF Callback
callback := tfsdkcallback.Register{
DeviceKey: tfsdkcrypto.PublicKey{},
ContractKey: tfsdkcrypto.PublicKey{},
DeviceInfo: tfsdk.DeviceInfo{},
DeviceIP: "",
DeviceLocation: nil,
AppUserUniqueID: "jonh.doe",
RegisterCodeUniqueID: nil,
}
const (
userName = "Jonh Doe" // This value should have a relation with AppUserUniqueID from the callback
)

var (
wrongDeviceToken = "" // Optional. The application server should expose an API that validates this token and allows the removal of the newly registered device.
)

if err := rtasSendRegistrationSuccessCallback(r.Context(), callback, userName, &wrongDeviceToken); err != nil {
panic(err)
}

w.WriteHeader(http.StatusOK)

return nil
}

func rtasSendRegistrationSuccessCallback(ctx context.Context, callback tfsdkcallback.Register, userName string, wrongDeviceToken *string) error {
tfSDKDeviceInfoToRTASDeviceInfo := func(deviceInfo tfsdk.DeviceInfo) *rtas.RegistrationCallbackRequestV2DeviceInfo {
registrationCallbackDeviceInfo := rtas.NewRegistrationCallbackRequestV2DeviceInfo(deviceInfo.Name, deviceInfo.Manufacturer, deviceInfo.Model, deviceInfo.OS, deviceInfo.OSVersion)
registrationCallbackDeviceInfo.MarketingName = &deviceInfo.MarketingName
return registrationCallbackDeviceInfo
}

body := *rtas.NewRegistrationCallbackRequestV3(*callback.RegisterCodeUniqueID, rtas.REGISTRATION_SUCCESS_V2, callback.AppUserUniqueID)
body.SetUserDisplayName(userName)
body.SetApplicationName(ApplicationName)
body.DeviceInfo = tfSDKDeviceInfoToRTASDeviceInfo(callback.DeviceInfo) // This method converts the TrustFactor SDK device into a RTAS device.
body.SetDeviceKey(callback.DeviceKey.ToString())
body.SetContractKey(callback.ContractKey.ToString())
body.WrongDeviceToken = wrongDeviceToken

// signalr connection properties
body.SetCloseConnectionOnReceive(true)
body.SetStoreInCache(true)
body.SetCacheExpiresAfterMinutes(15)

resp, err := RTASClient.RegistrationCallbackApi.RegistrationCallbackV3Execute(RTASClient.RegistrationCallbackApi.RegistrationCallbackV3(ctx).Body(body))
if err != nil {
var e rtas.GenericOpenAPIError
if errors.As(err, &e) {
fmt.Printf("RTAS Error\n%s\n%s\n", e.Error(), string(e.Body()))
return err
}
fmt.Printf("RTAS Error\n%s\n", err.Error())
return err
}
defer resp.Body.Close()

return nil
}