3.23.0 • Published 4 days ago

@slashid/slashid v3.23.0

Weekly downloads
-
License
MIT
Repository
-
Last release
4 days ago

Getting started

  1. Installation
  2. Examples

Installation

Library

$ npm install @slashid/slashid

The SDK supports Node version 12 and newer.

JavaScript bundle

If you want to include a bundle in your pages:

<script src="https://cdn.sandbox.slashid.com/slashid.js"></script>

Once the script loads, a global slashid object includes the {@link SlashID} and {@link User} classes and the {@link version} object;

HttpOnly

By default our SDK doesn't require a backend component to function, but this comes at the cost of the authentication token being accessible by JavaScript. If you run a security-sensitive application with long-lived tokens, we recommend the use of HttpOnly, SameSite=strict cookies. For these scenarios you can instead adopt our Gate module to add HttpOnly support to our SDK by default, without additional code in your frontend. Gate can run as a service in your backend or as a managed service in our infrastructure.

If you are interested in Gate, please contact us to find out more.

Examples

All the examples below assume you have:

  • a {@link SlashID} instance created with:
    // In a module script
    import * as slashid from "@slashid/slashid"
    const sid = new slashid.SlashID()
    or
    // In browsers
    <script src="https://cdn.sandbox.slashid.com/slashid.js"></script>
    <script>
        var sid = new slashid.SlashID()
    </script>
  • your /id-provided Organization ID in MY_OID:
    const MY_OID = "..." // provided on registration

Examples:

Register a new user with Passkeys (WebAuthn)

:::info Passkeys are the safest authentication method available today. It has built-in phishing prevention, it cannot be easily tampered with in most devices and can provide proof-of-humanness thus reducing the risk of bots.

Passkeys are intuitive and allows user to register with built-in sensors such as TouchID or FaceID. :::

We are going to register a new user with their phone number. We want to authenticate them with Passkeys on their mobile phone:

const user = await sid.id(
    MY_OID,
    {
        type: "phone_number",
        value: "+13337777777",
    },
    {
        method: "webauthn",
    }
)

SlashID will deliver a magic link to the given phone number. Upon opening the link the user will be prompted to create Passkeys credential with their method of choice (FaceID, fingerprint reader, FIDO keys, etc.) on the device they open the magic link on. The .id method will return as soon as the user registers with Passkeys successfully, or timeout in 2 minutes and throw an exception/error.

Alternatively email_address type handles are also supported with the equivalent method webauthn.

Authenticators

Users can perform Passkeys either with built-in authenticators (FaceID, fingerprint readers, etc.) or external security keys. The example above is the most compatible option, as it accepts both types. On the vast majority of devices the user will be prompted with a system dialog to choose which type of authenticator they prefer. You can opt to further reduce UX friction and skip the system dialog entirely by choosing which authenticator type your users should use:

const user = await sid.id(
    MY_OID,
    {
        type: "phone_number",
        value: "+13337777777",
    },
    {
        method: "webauthn",
        options: {
            attachment: "platform",
        },
    }
)

Using "platform" selects the device's built-in authenticator, whereas "cross_platform" selects external security keys. Not providing any attachment option is equivalent to selecting "any".

Please check out the example for choosing an authentication method to select a graceful fallback strategy in case the current device does not support Passkeys or does not have a built-in authenticator.

Alternatively email_address type handles are also supported with the exact same webauthn method.

Register a new user with a magic link

:::info Contrary to other authentication vendors, SlashID magic links allow to resume the browsing session in the original window so the user doesn't have to interrupt the flow. :::

We are going to register a new user with a magic link delivered to their phone number:

const user = await sid.id(
    MY_OID,
    {
        type: "email_address",
        value: "test@test.com",
    },
    {
        method: "email_link",
    }
)

SlashID will deliver a magic link to the given phone number. The .id method will return as soon as the user opens the link, or timeout in 2 minutes and throw.

Alternatively phone_number type handles are also supported with the equivalent method sms_link.

Register a new user with an OTP code via SMS

We are going to register a new user with an OTP security code delivered to their phone number.

:::info SMS OTP has the best conversion results when the user is registering or logging in from a mobile device. :::

:::caution Phone numbers are subject to hijacking, you shouldn't rely on SMS magic links or OTP as the only authentication factor for sensitive operations. :::

First we are going to need an input component to allow the user to insert the OTP code:

<label>OTP:</label>
<input id="otp_value" type="text" autocomplete="one-time-code" />
<button id="otp">Submit OTP</button>

Then we can proceed to trigger the authentication flow:

// optionally listen for the "otpSmsSent" event and render the OTP input field
sid.subscribe("otpSmsSent", () => {
    // render or enable the OTP input field
})

// optionally listen for the "otpIncorrectCodeSubmitted" event
sid.subscribe("otpIncorrectCodeSubmitted", () => {
    // let the user know they entered an incorrect OTP code
})

// let the SDK know once the OTP is submitted
document.getElementById("otp").onclick = (_) => {
    sid.publish("otpCodeSubmitted", document.getElementById("otp_value").value)
}

const user = await sid.id(
    MY_OID,
    {
        type: "phone_number",
        value: "+13337777777",
    },
    {
        method: "otp_via_sms",
    }
)

After calling the .id method to authenticate the user with an OTP code over SMS, SlashID will send a 6-digit OTP code to the given phone number. When the SMS is sent, SDK will emit an otpSmsSent event you can optionally subscribe to in order to present the user with the OTP input field.

When the user receives the OTP code and submits the value, you can publish the otpCodeSubmitted event to the SDK. If the OTP code is valid, this will cause the .id method to resolve. If the code is not valid, the SDK will emit the otpIncorrectCodeSubmitted event and continue to listen for the otpCodeSubmitted event until it receives the correct OTP code. After 2 minutes without receiving the correct code it will time out and the .id method will not resolve.

Register a new user with SSO

Please refer to our SSO guide for more details on how to use SSO with SlashID.

Login a registered user

We are going to let a returning user login with their previously registered phone number. In order to do this we want to authenticate them with Passkeys on their mobile phone:

const user = await sid.id(
    MY_OID,
    {
        type: "phone_number",
        value: "+13337777777",
    },
    {
        method: "webauthn",
    }
)

SlashID will deliver a magic link to the given phone number. Upon opening the link the user will be prompted to authenticate with their Passkeys credential on the device where they opened the magic link. open the magic link on**. The .id method will return as soon as the user authenticates with Passkeys successfully, or timeout after 2 minutes and throw an exception.

From the point of view of the user the experience of registering and logging in are also identical in this case. If you need to distinguish a new user from a returning user in order to customize your UX/UI, you can easily achieve that with:

if (user.firstLogin) {
    // it's a new user
} else {
    // it's a returning user
}

Alternatively email_address type handles are also supported with the equivalent method webauthn. In general you can reuse the examples for registering new users with Passkeys and registering new user with magic links to login returning users, with exactly the same code in all cases.

Login a user with Direct-ID

SlashID Direct-IDs allow users to land pre-authenticated on a webpage. With this functionality you can create resumable user flows, invitations, and targeted marketing campaigns that minimize UX friction.

:::info Direct-ID can be used to generate invitation links, 1-click checkout marketing campaigns and switch an user across different organizations.

In general Direct-ID is useful in scenarios where you want your users to land on a page pre-authenticated so they can focus on the call to action(CTA) and not interrupting the CTA flow to login. :::

To generate a Direct-ID token for a user you can use our REST APIs. The API endpoint returns a Direct-ID string, which can be embedded in a URL in the challenges query parameter.

In the frontend, you can authenticate a user through Direct-ID by calling getUserFromURL. The function exchanges a Direct-ID token from the challenges query parameter for an access token with the SlashID backend.

In this way, users can use a link with Direct-ID to land on a target page already authenticated.

const sid = new window.slashid.SlashID();
let user = undefined;

try {
    user = await sid.getUserFromURL();
    if(user.validateToken().valid)
        //user is authenticated and the access token is valid

}catch(e) {
}

Choose an authentication method

The availability of authentication methods for {@link SlashID.id | .id} varies:

  • the type of handle ("phone_number", "email_address") affects the remote methods; e.g. you can use "otp_via_sms" or "sms_link" if your handle has type "phone_number", "email_link" if your handle has type "email_address";
  • whether the device supports Passkeys at all affects the availability of the WebAuthn method ("webauthn");

You can check at runtime which authentication methods are available, in accordance to your needs, by catching errors of type slashid.errors.InvalidAuthenticationMethodError. For example you could:

  • prefer Passkeys with built-in authenticator if available, fallback to magic link via SMS otherwise:
    try {
        user = await sid.id(handle, { method: "webauthn", options: { attachment: "platform" } })
    } catch (e) {
        if (e instanceof slashid.errors.InvalidAuthenticationMethodError) {
            // no builti-in Passkeys authenticator available on this device
            // => fallback to magic link via SMS
            user = await sid.id(handle, { method: "sms_link" })
        } else {
            throw e
        }
    }
  • for security reasons require Passkeys, regardless of authenticator type, but fail if not available:
    try {
        user = await sid.id(handle, { method: "webauthn" })
    } catch (e) {
        if (e instanceof slashid.errors.InvalidAuthenticationMethodError) {
            // Passkeys is not available on this device, fail authentication attempt
        } else {
            throw e
        }
    }
  • just use one of the always-available methods, e.g. for a "phone_number" handle you could always choose "otp_via_sms" or "sms_link";
  • let your users choose with the UX/UI of your choice;

Alternatively, you can also statically check which authentication methods are available given your choice of handle type using {@link SlashID.getAvailableAuthenticationMethods | .getAvailableAuthenticationMethods}.

Display existing users

Assuming your UI includes an input field for the user handle, you may want to display hints of handles which previously registered/authenticated successfully. We are going to fetch that list from {@link SlashID.getAvailableIdentifiers | .getAvailableIdentifiers} and display a drop-down list for an input field:

  1. Let's create an initially empty <input> field and its corresponding <datalist> element for the hints in your HTML page:
    ...
    <input id="..." type="text" list="available_identifiers" />
    <datalist id="available_identifiers"></datalist>
    ...
  2. Update the drop-down contents whenever you see fit:

    ...
    // Fetch the previously-authenticated handles, if any
    const handles = await sid.getAvailableIdentifiers()
    
    // Populate the <datalist> with the handles
    const options = handles.map((handle) => {
      const option = document.createElement("option")
      option.value = handle.value
      return option
    })
    document.getElementById("available_identifiers").replaceChildren(...options)
    ...

Associate an e-mail address to a user

We have users registered with phone numbers as handles, but we also want to allow them to login with their e-mail address:

// First register or authenticate the user
const user = await sid.id(...)

// Collect the e-mail address with the UX/UI of your choice
const emailAddress = ...

// Last, attach the e-mail address to the user
await user.mfa({
    type: "email_address",
    value: emailAddress
})

SlashID will deliver a magic link to the given e-mail address to verify it. The {@link User.mfa | .mfa} method will return as soon as the user opens the link, or timeout in 2 minutes and throw. On success, going forward the user will be able to authenticate with the newly-added e-mail address in addition to any other previously-known handles.

The above snippet also works in case your users register/authenticate with an e-mail address already, but you want to attach a second, or Nth one. You can follow the same procedure to attach additional phone numbers instead of e-mail addresses.

Perform Multi-Factor Authentication

The current user already registered or logged in with a magic link delivered to their phone number. Now we need to perform a sensitive operation and before we do that we want to make sure the user is physically present. In order to do that we ask them to re-authenticate with Passkeys:

await user.mfa({
    method: "webauthn",
})

The user will be prompted to authenticate with Passkeys. When they do the method returns successfully.

You can check how many and which methods a user has been authenticated with at any time with:

const authenticatedMethods = user.authentication
// => authenticatedMethods === ["sms_link", "webauthn"]

Verify token validity

Given a user token, you can verify its validity by calling the validateToken method on the user object.

The function returns an object with the following fields

  • valid: a boolean indicating whether the token is valid or not
  • invalidity_reason: the reason why a token is invalid, if the valid field is false
  • expires_in_seconds: seconds until the token expires, or not present if the token is invalid
  • expires_at: token expiration timestamp in UTC, or not present if the token is not valid

Below is a simple example:

// First register or authenticate the user
const user = await sid.id(...)

const tokenValidityInfo = user.validateToken()

if(tokenValidityInfo.valid) {
  // Take appropriate action if the token is invalid
  // For example, if the token has expired, ask the user to reauthenticate
}

Working with user attributes

Users can have any number of custom attributes attached to them. Attributes are stored in buckets so to access them you first need to get a {@link Types.Bucket} by calling {@link User.getBucket | .getBucket}. A bucket name is required - if you don't specify a bucket the default value is "end_user_read_write". All user attributes are encrypted with a dedicated key which is itself protected by multiple layers of per organization keys. This guarantees the ability to comply with GDPR via crypto-shredding and prevents data leaks.

Fetching user attributes

const bucket = user.getBucket(DefaultBucketName.end_user_read_write)
const attrs = await bucket.get()
// => attrs === {"attr1": "value1", "attr2": 123456789, "attr3": true}

If you want you can also retrieve them selectively:

const bucket = user.getBucket(DefaultBucketName.end_user_read_write)
const attrs = await bucket.get(["attr2", "attr3"])
// => attrs === {"attr2": 123456789, "attr3": true}

Storing attributes

const bucket = user.getBucket(DefaultBucketName.end_user_read_write)
const attrs = await bucket.get()
// => attrs === {}

await bucket.set({ attr1: "value1" })

const attrs = await bucket.get()
// => attrs === {"attr1": "value1"}

Get user groups

Users can belong to zero or multiple groups. This information is embedded in the user token and it can be retrieved with a call to {@link User.getGroups | .getGroups}:

const groups = user.getGroups()
// => groups === ['group-name']

Persist users across browser sessions

The validity of a user token is dictated by your Organization preferences and defaults to 30 days. We want to persist the user token in order to avoid asking them to re-authenticate when returning to our website. In order to achieve that we are going to resort to Window.localStorage:

let user = undefined

// On load we check whether we have a token
const prevToken = window.localStorage.getItem("MY_USER_TOKEN")
if (prevToken) {
    // There's a token, just re-create the user
    user = new slashid.User(prevToken)
} else {
    // Register or authenticate the user
    user = await sid.id(...)
}

// After successful authentication we store the token for the next session
window.localStorage.setItem("MY_USER_TOKEN", user.token)
3.23.0

5 days ago

3.23.0-next.0

23 days ago

3.23.0-next.1

20 days ago

3.23.0-totp.0

24 days ago

3.22.0

25 days ago

3.21.3-rc.0

1 month ago

3.21.2

1 month ago

3.21.1

1 month ago

3.21.0

1 month ago

3.21.0-rc.0

1 month ago

3.21.0-rc.1

1 month ago

3.21.0-next.1

1 month ago

3.21.0-next.5

1 month ago

3.21.0-next.4

1 month ago

3.21.0-next.3

1 month ago

3.21.0-next.2

1 month ago

3.21.0-next.0

1 month ago

3.20.2

1 month ago

3.20.1

1 month ago

3.20.0

1 month ago

3.19.1

2 months ago

3.19.1-next.20

2 months ago

3.19.0

2 months ago

3.19.0-next.4

2 months ago

3.19.0-next.5

2 months ago

3.19.0-next.6

2 months ago

3.19.0-next.7

2 months ago

3.19.0-next.8

2 months ago

3.19.1-next.10

2 months ago

3.19.1-next.9

2 months ago

3.19.1-next.8

2 months ago

3.19.1-next.7

2 months ago

3.19.1-next.6

2 months ago

3.19.1-next.5

2 months ago

3.19.1-next.4

2 months ago

3.19.1-next.3

2 months ago

3.19.1-next.2

2 months ago

3.19.1-next.1

2 months ago

3.19.1-next.0

2 months ago

3.19.0-next.0

2 months ago

3.19.0-next.1

2 months ago

3.19.0-next.2

2 months ago

3.19.0-next.3

2 months ago

3.18.3

2 months ago

3.18.3-beta.5

2 months ago

3.18.3-beta.2

2 months ago

3.18.3-beta.3

2 months ago

3.18.3-beta.4

2 months ago

3.18.3-beta.1

2 months ago

3.18.3-beta.0

2 months ago

3.18.2

3 months ago

3.18.2-beta.0

3 months ago

3.18.1

3 months ago

3.17.4-beta.4

3 months ago

3.17.4-beta.5

3 months ago

3.18.0

3 months ago

3.17.3-next.0

3 months ago

3.17.4-beta.2

3 months ago

3.17.4-beta.3

3 months ago

3.17.4-beta.1

3 months ago

3.17.4-beta.0

3 months ago

3.17.3

3 months ago

3.17.2

4 months ago

3.17.1

4 months ago

3.17.0

5 months ago

3.15.0

6 months ago

3.13.2

8 months ago

3.13.1

8 months ago

3.9.0

10 months ago

3.10.0

9 months ago

3.12.0

9 months ago

3.14.0-next.0

7 months ago

3.14.1

7 months ago

3.14.0

7 months ago

3.8.3

10 months ago

3.8.2

10 months ago

3.16.0

5 months ago

3.14.2

6 months ago

3.11.0

9 months ago

3.13.0

8 months ago

3.8.1

10 months ago

3.8.0

11 months ago

3.7.0

11 months ago

3.6.5

11 months ago

3.6.4

12 months ago

3.6.3

1 year ago

3.6.2

1 year ago

3.6.1

1 year ago

3.6.0

1 year ago

3.5.0

1 year ago

3.4.0

1 year ago

3.3.2

1 year ago

3.3.0

1 year ago

3.2.0

1 year ago

3.1.0

1 year ago

3.0.0

1 year ago

2.0.0

1 year ago

1.10.0

1 year ago

1.9.3

1 year ago

1.9.2

1 year ago

1.8.2

2 years ago

1.8.1

2 years ago

1.8.0

2 years ago

1.7.1

2 years ago

1.7.0

2 years ago

1.6.1

2 years ago

1.6.0

2 years ago

1.5.0

2 years ago

1.4.4

2 years ago

1.4.3

2 years ago

1.4.2

2 years ago

1.4.1

2 years ago

1.4.0

2 years ago

1.3.3

2 years ago

1.3.2

2 years ago

1.3.1

2 years ago

1.3.0

2 years ago

1.2.0

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago