Affinidi Login - Passwordless Authentication for WordPress

In this guide, learn how to enable passwordless authentication on the WordPress site using Affinidi Login.

Affinidi Login - Passwordless Authentication provides an alternative way to securely authenticate to WordPress using Affinidi Vault as the identity provider managed by the end-user. It integrates with Affinidi Login and uses the PKCE standard to enable passwordless login for a seamless user experience.

Before you begin
  1. Set up Affinidi Vault account. Follow the guide below if you haven’t set it up yet.
Set up Affinidi Vault

Set up an Affinidi Vault account using the Web Vault or install the Mobile Vault (for Android).

The same setup steps for Mobile Vault.

  1. Click on Get started if you are creating a new account, or click on Restore from Backup if you have an existing backup of your Affinidi Vault. Provide the Passphrase to secure your Affinidi Vault.

You have the option to enable Biometrics to unlock your Affinidi Vault easily instead of using Passphrase.

Affinidi Vault Setup
  1. Enter your email address to register with the Affinidi Vault. An OTP will be sent to this email for verification.
Affinidi Vault Passphrase
  1. Enter the OTP sent to the email you have provided for verification to complete the setup.
Affinidi Vault Email Verification

After successfully providing the OTP, you are redirected to the Affinidi Vault dashboard.

  1. Install and Setup the latest WordPress or use the Docker sample for WordPress on your machine.

  2. Optionally, install the Affinidi CLI. Follow the guide below if you haven’t installed yet.

Set up Affinidi CLI
  1. Download and install NodeJS on your machine if you haven’t set it up yet.
  1. Install Affinidi CLI using Node Package Manager (npm).
npm install -g @affinidi/cli
  1. Verify that the installation is successful.
affinidi --version

Download and Install the Plugin

Affinidi Login - Passwordless Authentication plugin is available as an open-source project on GitHub and published in the WordPress Plugin Directory .

To install the plugin in WordPress:

  1. Download the Affinidi Login - Passwordless Authentication plugin from the WordPress Plugin Directory.
  2. Log in to the Admin page of your WordPress site.
  3. Go to the Plugins -> Add New Plugin page.
  4. Upload the zip file and activate the plugin.

Alternatively, if you have an existing WordPress website or are already logged in to a WordPress site:

  1. Go to the Plugins -> Add New Plugin page.
  2. Search for the plugin “Affinidi Login” and click Install Now.
  3. After installation, click on Activate button.

After successfully installing the plugin, go to the Settings -> Affinidi Login page to start setting up the Affinidi Login integration.

Create Login Configuration

To create a Login Configuration, you can either use Affinidi CLI or  Affinidi Portal.

Expand the section below for your preferred method:

Name: WordPress App

Redirect URIs: Get the Redirect URI provided in the plugin based on your WordPress instance.

e.g. https://[SITE_URL]/?auth=affinidi

Auth Method: none

Using Affinidi CLI
  1. Log in to Affinidi CLI by running:
affinidi start
  1. Once you have successfully logged in, create the Login Configuration by running:
affinidi login create-config \
--name='WordPress App' \
--redirect-uris='{WordPress_Redirect_URI}' \
--token-endpoint-auth-method=none
  • --name is what you want your login configuration to be called.
  • --redirect-uris is the URL on your application where the user gets redirected after the successful authentication. Replace {WordPress_Redirect_URI} with the redirect URI value generated by the plugin based on your WordPress instance.
  • --token-endpoint-auth-method is the type of the authentication method to use, for WordPress plugin, we are using PKCE standard so we set the Auth Method to none.

Sample response:

{
  "ari": "ari:identity:ap-southeast-1:687b8872-a618-dt63-8978-e72ac32daeb1:login_configuration/c4f74d936cd31bde1c1fd3c1050bb76s",
  "projectId": "687b8872-a618-4e52-8978-e72ac32daec2",
  "configurationId": "c4f74d936cd31bde1c1fd3c1050bb62d",
  "name": "...",
  "auth": {
    "clientId": "<AUTH.CLIENT_ID>",
    "clientSecret": "<AUTH.CLIENT_SECRET>",
    "issuer": "https://<PROJECT_ID>.apse1.login.affinidi.io"
  },
  "redirectUris": [
    "..."
  ],
  "clientMetadata": {
    "name": "Login Config Name",
    "logo": "https://login.affinidi.com/default-client-logo.svg",
    "origin": "https://example.com"
  },
  "creationDate": "2023-08-11T06:26:37Z",
  "tokenEndpointAuthMethod": "none"
}

Learn more on how to manage your Login Configurations using Affinidi CLI.

Using Affinidi Portal
Create new Login Configuratioin
  1. Go to  Affinidi Login under the Services section.

  2. Click on the Create Login Configuration and provide the required details.

  • Name is the string that describes your login configuration.
  • Redirect URIs is the URL on your application where the user gets redirected after the successful authentication.
  1. Click on create and confirm if all the details are correct.
Login Configuratation new client
  1. After confirming the details, another popup shows the Client ID and Client Secret for your Login Configuration. Copy the generated Client Credentials and use them to integrate with Affinidi Login.

  2. After copying the Client ID and Client Secret and closing the popup, you are redirected back to the Affinidi Login page.

Login Configuration uses the default Presentation Definition (presentationDefinition) and ID Token Mapping (idTokenMapping) that is used to request the user’s email address during the authentication flow.

Presentation Definition and ID Token Mapping

Use the templates below based on your Affinidi Login WordPress plugin configuration.

WordPress User Profile

To sync user’s data from the Affinidi Vault to the WordPress user table, use the following Presentation Definition and ID Token Mapping template below:

Basic Profile
Presentation Definition
{
  "id": "user_profile_vc",
  "submission_requirements": [
    {
      "rule": "pick",
      "min": 1,
      "from": "A"
    }
  ],
  "input_descriptors": [
    {
      "id": "email_vc",
      "name": "Email VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^Email$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.email"
            ],
            "purpose": "Check if VC contains email field",
            "filter": {
              "type": "string"
            }
          },
          {
            "path": [
              "$.issuer"
            ],
            "purpose": "Check if VC Issuer is Trusted",
            "filter": {
              "type": "string",
              "pattern": "^did:key:zQ3shtMGCU89kb2RMknNZcYGUcHW8P6Cq3CoQyvoDs7Qqh33N"
            }
          }
        ]
      }
    },
    {
      "id": "givenname_vc",
      "name": "Given Name VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^HITGivenName$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.givenName"
            ]
          }
        ]
      }
    },
    {
      "id": "familyname_vc",
      "name": "Family Name VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^HITFamilyName$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.familyName"
            ]
          }
        ]
      }
    }
  ]
}
ID Token Mapping
[
  {
    "sourceField": "$.credentialSubject.email",
    "idTokenClaim": "$.email",
    "inputDescriptorId": "email_vc"
  },
  {
    "sourceField": "$.credentialSubject.givenName",
    "idTokenClaim": "$.given_name",
    "inputDescriptorId": "givenname_vc"
  },
  {
    "sourceField": "$.credentialSubject.familyName",
    "idTokenClaim": "$.family_name",
    "inputDescriptorId": "familyname_vc"
  }
]

E-Commerce Customer Profile

If you have enabled e-commerce support, use the Presentation Definition and ID Token Mapping below to include the user’s address and contact info.

Full User Profile
Presentation Definition
{
  "id": "user_profile_vc",
  "submission_requirements": [
    {
      "rule": "pick",
      "min": 1,
      "from": "A"
    }
  ],
  "input_descriptors": [
    {
      "id": "email_vc",
      "name": "Email VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^Email$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.email"
            ],
            "purpose": "Check if VC contains email field",
            "filter": {
              "type": "string"
            }
          },
          {
            "path": [
              "$.issuer"
            ],
            "purpose": "Check if VC Issuer is Trusted",
            "filter": {
              "type": "string",
              "pattern": "^did:key:zQ3shtMGCU89kb2RMknNZcYGUcHW8P6Cq3CoQyvoDs7Qqh33N"
            }
          }
        ]
      }
    },
    {
      "id": "streetaddress_vc",
      "name": "Street Address VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^HITStreetAddress$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.streetAddress"
            ]
          }
        ]
      }
    },
    {
      "id": "city_vc",
      "name": "City VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^HITLocality$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.locality"
            ]
          }
        ]
      }
    },
    {
      "id": "postalcode_vc",
      "name": "Postal Code VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^HITPostalCode$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.postalCode"
            ]
          }
        ]
      }
    },
    {
      "id": "country_vc",
      "name": "Address VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^HITCountry$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.country"
            ]
          }
        ]
      }
    },
    {
      "id": "givenname_vc",
      "name": "Given Name VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^HITGivenName$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.givenName"
            ]
          }
        ]
      }
    },
    {
      "id": "familyname_vc",
      "name": "Family Name VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^HITFamilyName$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.familyName"
            ]
          }
        ]
      }
    },
    {
      "id": "phonenumber_vc",
      "name": "Phone Number VC",
      "purpose": "Check if VC data contains necessary fields",
      "group": [
        "A"
      ],
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "^HITPhoneNumber$"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.phoneNumber"
            ]
          }
        ]
      }
    }
  ]
}
ID Token Mapping
[
  {
    "sourceField": "$.credentialSubject.email",
    "idTokenClaim": "$.email",
    "inputDescriptorId": "email_vc"
  },
  {
    "sourceField": "$.credentialSubject.streetAddress",
    "idTokenClaim": "$.address.street_address",
    "inputDescriptorId": "streetaddress_vc"
  },
  {
    "sourceField": "$.credentialSubject.locality",
    "idTokenClaim": "$.address.locality",
    "inputDescriptorId": "city_vc"
  },
  {
    "sourceField": "$.credentialSubject.postalCode",
    "idTokenClaim": "$.address.postal_code",
    "inputDescriptorId": "postalcode_vc"
  },
  {
    "sourceField": "$.credentialSubject.country",
    "idTokenClaim": "$.address.country",
    "inputDescriptorId": "country_vc"
  },
  {
    "sourceField": "$.credentialSubject.givenName",
    "idTokenClaim": "$.given_name",
    "inputDescriptorId": "givenname_vc"
  },
  {
    "sourceField": "$.credentialSubject.familyName",
    "idTokenClaim": "$.family_name",
    "inputDescriptorId": "familyname_vc"
  },
  {
    "sourceField": "$.credentialSubject.phoneNumber",
    "idTokenClaim": "$.phone_number",
    "inputDescriptorId": "phonenumber_vc"
  }
]

Configure the WordPress Plugin

After successfully creating the Login Configuration, the next is to set up the plugin by providing the following information on the Affinidi Login settings page.

Affinidi Login WordPress Settings

Integration settings

  • Client ID is the Client ID generated by Login Configuration.

  • Issuer URL is the Issuer URL generated by Login Configuration.

The plugin implements the PKCE flow; in this case, the Auth Method of the Login Configuration must be set to None.

Plugin settings

  • Redirect user to Origin Page: once enabled, the user is redirected to where they have clicked the Affinidi Login button. It is useful if you have customised the page where the Affinidi Login will display and enabled E-Commerce support.

  • Restrict flow to log in only (new users will not be allowed to signup): this setting is based on the WordPress General Settings “Anyone can register”, if this is enabled, Affinidi Login will allow signups.

Once successfully configured, the Affinidi Login button on the WordPress page will trigger the authentication flow and authenticate users using their Affinidi Vault as the Identity Provider.

Affinidi Login WordPress Login Form

Shortcode

To render the Affinidi Login on any page, use the Affinidi Login button shortcode:

[affinidi_login]

Supported E-Commerce Plugins

The plugin detects if there’s an activated supported E-Commerce plugin on your WordPress site and displays the Admin settings.

WooCommerce Support

Affinidi Login for WordPress plugin supports WooCommerce flow, including customer profile creation and authenticating customers into the store using their Affinidi Vault account.

  • Sync customer profile from Vault : determines whether to sync the user’s profile data from Affinidi Vault into the WooCommerce customer profile upon successful login. You can sync billing or shipping or sync only when you sign up.

  • Display Affinidi Login button: determines whether to display the Affinidi Login button on My Account and Checkout page. Developers can opt-in to use the shortcode to customise the Login page for the users.

  • Affinidi Login button header (Login Form): a configurable header text displayed at the top of the Affinidi Login button when rendered on the WooCoomerce Login Form.

  • Affinidi Login button header (Registration Form): a configurable header text displayed at the top of the Affinidi Login button when rendered on the WooCoomerce Registration Form.