Support Access Diagrams
TrustedLogin is designed to be simple, secure, and easy way for users to grant access to a support team. Thanks to the design of the service, login credentials are end-to-end encrypted and unable to be accessed by TrustedLogin.
Below are simplified visualizations of the flow of data between the various components of TrustedLogin.
The three parts of TrustedLogin:
- TrustedLogin service, running on app.trustedlogin.com
- Connector plugin, running on the software provider's website
- Client, either as a stand-alone TrustedLogin plugin or the SDK integrated with a WordPress plugin or theme
Together, these three components allow for site access to be granted securely and with minimal effort.
Support Access Flow
What happens when a customer or client grants access to their website:
Step 1: User Grants Access
User grants access to Vendor via the Client SDK.
This creates a user in WordPress with the defined roles. A "User Identifier" is created and a hash is stored in the WordPress user meta (see \TrustedLogin\Client\SupportUser::setup()
). The User Identifier will be used when the Vendor logs in.
In addition, a Secret ID
is generated and added to the usermeta. This hash is used as the storage ID when the site is added to the SaaS Vault.
Step 2: Public Key is Requested
The Client SDK requests the public key from the wp-json/trustedlogin/v1/public_key
endpoint from the Vendor's website.
The public key is fetched by default from the URL defined in the vendor/website
setting. It's possible to override this using the trustedlogin/{namespace}/vendor/public_key/endpoint
filter.
Step 3: Public Key is Generated
The public key request is handled by \TrustedLogin\Vendor\Endpoints\PublicKey::get()
, which uses \TrustedLogin\Vendor\Encryption::generateKeys()
to generate two sets of encryption keys (crypto_sign
and crypto_box
key pairs) but only returns the crypto_box
public key.
Step 4: Envelope Created & Encrypted
The envelope is generated and encrypted using Vendor public keys.
The Client \TrustedLogin\Client\Envelope::get()
uses \TrustedLogin\Client\Encryption::generate_keys()
, \TrustedLogin\Client\Encryption::encrypt()
, \TrustedLogin\Client\Encryption::get_vendor_public_key()
.
The Vendor's public key is stored in the Client using WordPress transients.
Step 5: Client POST
s Envelope to SaaS
The Client SDK, using, \TrustedLogin\SiteAccess::sync_secret()
makes a POST
request to https://app.trustedlogin.com/api/v1/sites
. This is handled by \App\Http\Controllers\SiteController::createSite()
. See endpoint documentation.
Step 6: SaaS Stores Envelope in Vault
In the SaaS, SiteController::createSite()
generates Vault tokens to create a secret and stores the envelope in the Vault.
The SiteCreatedEvent()
event is triggered in Laravel. This logs the event to Elasticsearch.
The successful response from the SaaS to the Client is:
{ "success": true }
The unsuccessful response is:
{ "message": "'Error', or \Exception::getMessage() value." }
Support Logging Into a Customer/Client Website
Step 1: A Person Submits Site Access Form
The form submits a POST
HTTP request via AJAX that is received by the TrustedLogin\Vendor\AccessKeyLogin::handle()
method.
Receiving that request, TrustedLogin\Vendor\AccessKeyLogin::verifyGrantAccessRequest()
verifies that the nonce is valid and that the request is coming from inside the site.
In addition, TrustedLogin\Vendor\Traits\VerifyUser::verifyUserRole()
checks to make sure the user is logged-in and has one or more of the roles that are required to access the site.
Step 2: Vendor Requests List of Matching Site IDs
The Connector plugin requests a list of Site IDs that match that access key by sending a POST
request to the accounts/{$account_id}/sites/
SaaS endpoint.
The request includes an Authorization: Bearer {hashed private key}
header as well as the following body:
{
"searchKeys": [ "The submitted Site Access Key"]
}
Step 3: SaaS Verifies Request and Returns Site IDs
The SaaS verifies the hashed Bearer token passed in the Authorization
header using \App\Http\Middleware\CheckPrivateKey::handle()
.
Then the SaaS checks to make sure the Vendor account isn't in "Pause Mode", which is triggered by brute force attempts. When Pause Mode is enabled, new access may be granted, but site login and lookups are restricted. See /Http/Middleware/CheckPauseMode.php
.
\App\Http\Controllers\SiteController::siteByLicenseOrAccessKeys()
is called to retrieve a list of sites stored in the Vault. Read the endpoint documentation.
An array of Secret IDs is returned. These are not the envelope itself; Secret IDs refer to the IDs of Vault secrets.
{
"accessKey1": [
"secretId1"
],
"accessKey2": [
"secretId2",
"secretId3"
]
"accessKey3": [
"secretId2",
"secretId3"
]
}
Step 4: Connector Plugin Requests Matching Envelope(s) from SaaS
The Connector plugin uses the Secret IDs to retrieve the envelopes from the Vault.
In addition to the Bearer token, the request generates a signed nonce in TrustedLogin\Vendor\Encryption::createIdentityNonce()
. The method:
- Generates a cryptographic nonce (in
TrustedLogin\Vendor\Encryption::generateNonce()
usingrandom_bytes()
), - Signs the nonce with the
sign_private_key
pair (inTrustedLogin\Vendor\Encryption::sign()
, usingsodium_crypto_sign_detached()
), and - Verifies that the signed nonce has been properly generated (using
sodium_crypto_sign_verify_detached()
)
The nonce and signed nonce are both sent in the request, helping to verify that this site is indeed the sender of the data.
A POST
request is made to sites/{account_id}/{secret_id}/get-envelope
to retrieve the envelope from the Vault. The Bearer token is passed in the Authorization
header, and a X-TL-TOKEN
header is also sent. The X-TL-TOKEN
header is a hash of the Vendor private and public keys.
Step 5: SaaS Verifies Request and Returns Envelope(s)
The SaaS verifies the hashed Bearer token and ensures that the Vendor account isn't in Pause Mode (see Step 3).
The SaaS verifies the signed nonce using \App\Http\Middleware\CheckSignedNonce::handle()
.
The request is handled by \App\Http\Controllers\SiteController::getEnvelope()
, which retrieves the envelope from the Vault.
Inside getEnvelope()
, the X-TL-TOKEN
token is verified against the Vendor's account information.
The envelope with encrypted credentials is returned to the Vendor.
Step 6: Connector Plugin Receives & Decrypts Envelope
The Connector plugin receives the envelope. It includes the Site URL associated with the Site Access Key but not the endpoint, which is required to log in.
The Connector plugin decrypts the envelope and extracts the credentials, then cryptographically generates the URL to access Client site (using TrustedLogin\Vendor\TrustedLoginService::envelopeToUrl()
).
The site URL and the access parts are returned as an AJAX response, completing the request started in Step 1.
Step 7: Connector Plugin POST
s to Client Site
A temporary form is created using JavaScript with the Client Site URL set as the form action
property. A POST
request is submitted, preventing the submitted data from being logged.
The form submits the following to the Client Site URL:
[
method: 'POST',
action: 'trustedlogin',
endpoint: {endpoint},
identifier: {identifier}
]
When the form submits, the user on the Vendor website is automatically redirected to the Client site.
Step 8: Client Verifies Login Request
The login request is received by the Client in {Client}\TrustedLogin\Endpoint::maybe_login_support()
.
The SDK performs security checks, including:
- The raw User Identifier value is found (using
{Client}\TrustedLogin\Endpoint::get_user_identifier_from_request()
) and then verified (using{Client}\TrustedLogin\SecurityChecks::verify()
). - The SDK checks whether a brute-force attack is underway (via
{Client}\TrustedLogin\SecurityChecks::do_lockdown()
). If an attack is determined, the code prevents login and enters Lockdown Mode. See the Security doc for more information about Lockdown Mode. - The SDK determines whether the user access period has expired. If it has, the user is deleted and the login is prevented.
- The SDK sends a request to the SaaS to confirm that the validity of the request.
The following information is sent to sites/{secret_id}/verify-identifier
using a HTTP POST
request:
{secret_id}
Refers to the Secret ID stored in user meta. It is returned using {Client}\TrustedLogin\SupportUser::get_secret_id()
.
{
'timestamp': time(),
'user_agent': $_SERVER['HTTP_USER_AGENT'],
'user_ip': $this->get_ip(),
'site_url': get_site_url(),
}
Step 9: SaaS Also Verifies Login Request
The SaaS receives the verify-identifier
request and processes it using App\Http\Controllers\VerifyIdentifierController::handle()
.
The method verifies that the secret still exists in the Vault (it hasn't been deleted), and that the Vendor account is not in Pause Mode.
If success, the SaaS returns an empty JSON response []
with a 204
HTTP status code.
Possible error responses are indicated using the HTTP status codes 423
and 404
:
423
: The Vendor account is in Pause Mode404
: The Secret ID does not match any secrets in the Vault
Step 10: Client Logs User In
If the security checks pass in Step 8 and 9, the SDK calls {Client}\TrustedLogin\Endpoint::login()
to log the support user in.
The user is logged-in by calling wp_set_current_user()
, wp_set_auth_cookie()
and do_action( 'wp_login' )
.
Yay! 🎉 The user's now logged-in.
Step 11: Action Is Triggered
After login, the SDK triggers the following WordPress action: trustedlogin/{namespace}/logged_in
. This allows other plugins to perform actions and to trigger webhooks.
The SDK hooks into the action to run any webhooks configured in the Config array.
Revoke Login
At any time, a website administrator may revoke TrustedLogin access. When access is revoked, the Client sends a HTTP DELETE
request to the sites/{secret_id}
endpoint along with a X-TL-TOKEN
header.
The body of the request is:
{
'publicKey': {Client SDK API Key}
}
If the public key has been cycled, the request will fail.
Handled by \App\Http\Controllers\SiteController::deleteSite()
.
Possible responses are indicated using the HTTP status codes:
201
: Secret successfully deleted200
: Secret failed to be deleted in\App\Http/Clients/Vault::deleteSite()
404
: The Secret ID does not match any secrets in the Vault or there500
: An exception occurred