@slashid/slashid
Version:
Client SDK for the /id platform
1,018 lines (758 loc) • 38 kB
Markdown
# Introduction
1. [Getting started](#getting-started)
2. [Advanced Options](#advanced-options)
3. [Examples](#examples)
## Getting started
### 1. Installation
#### Library
```bash
$ npm install /slashid
```
The SDK supports Node version 12 and newer.
#### JavaScript bundle
If you want to include a bundle in your pages:
```html
<script src="https://cdn.slashid.com/slashid.js"></script>
```
Once the script loads, a global `slashid` object includes the { SlashID} and { User} classes and the { version} object;
### 2. Initialize the SDK
Depending on the way you installed the SDK, there are two ways to get the { SlashID} constructor:
- as a named export from the `/slashid` package:
```js
import { SlashID } from "@slashid/slashid"
// ...
```
- by including the JavaScript bundle:
```html
<script src="https://cdn.slashid.com/slashid.js"></script>
<script>
const SlashID = window.slashid.SlashID
// ...
</script>
```
### 3. Add Login functionality
Once the SDK has been initialized, authenticating or registering a user is as simple as:
```js
const user = await sid.id(
MY_OID,
{
type: "email_address",
value: "test@test.com",
},
{
method: "email_link",
}
)
```
## Advanced Options
### Choosing the environment
Once you have the constructor, you can create a { SlashID} instance in the following way:
```js
const sid = new SlashID({ oid: "MY_OID" })
```
This will use the `production` environment by default. If you want to use the `sandbox` environment instead, you can pass the `environment` option to the constructor:
```js
const sid = new SlashID({ oid: "MY_OID", environment: "sandbox" })
```
Alternatively you could provide your own custom environment by specifying the relevant URLs:
```js
const sid = new SlashID({
oid: "MY_OID",
environment: {
baseURL: "https://api.custom.com",
sdkURL: "https://sdk.custom.com/sdk.html",
},
})
```
**Note:** the `environment` option was introduced in `/slashid@3.18.0`. If you are using an older version of the SDK, you can use the `baseURL` and `sdkURL` options instead:
```js
const sid = new SlashID({
oid: "MY_OID",
baseURL: "https://api.custom.com",
sdkURL: "https://sdk.custom.com/sdk.html",
})
```
### 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.
### Server-side rendering (SSR)
The SDK by itself is not responsible for rendering any kind of UI - it gives you access to multiple authentication methods and handles the authentication flow. These methods depend on browser APIs so by design the `SlashID` class cannot be compatible with server-side rendering (SSR).
Instead, we export SSR-friendly modules using the `SSR` namespace - these modules are tested to work with frameworks like NextJS and Remix. The `SSR` namespace is available in the `/slashid` package:
```ts
import { SSR } from "@slashid/slashid"
const user = new SSR.User("<USER_TOKEN>")
// perform SSR friendly operations with the user object
```
SSR-friendly modules are documented [here](https://developer.slashid.dev/docs/access/sdk/modules#ssr).
### User Analytics
User analytics are enabled by default.
We've built user analytics right into SlashID. We automatically track user activity surrounding sign-up & log-in so we can give you metrics like monthly active users (MAU), returning users, and new users.
Further, we record how people are choosing to authenticate with SlashID and expose this data to you so you can understand user preferences regarding authentication methods, and crucially: any friction caused by requiring that a particular authentication method or combination of authentication methods (MFA) be used.
You can learn more in the [User Analytics guide](/docs/access/guides/analytics), and the [Analytics reference documentation](/docs/access/sdk/classes/Types.Analytics).
## Server-side rendering (SSR)
The SDK by itself is not responsible for rendering any kind of UI - it gives you access to multiple authentication methods and handles the authentication flow. These methods depend on browser APIs so by design the `SlashID` class cannot be compatible with server-side rendering (SSR).
Instead, we export SSR-friendly modules using the `SSR` namespace - these modules are tested to work with frameworks like NextJS and Remix. The `SSR` namespace is available in the `/slashid` package:
## Examples
All the examples below assume you have created a { SlashID} instance in one of the ways described above and have your Organization ID in `MY_OID`.
Examples:
- [Register a new user with Passkeys (WebAuthn)](#register-a-new-user-with-passkeys-webauthn)
- [Register a new user with a magic link](#register-a-new-user-with-a-magic-link)
- [Register a new user with OTP via SMS](#register-a-new-user-with-an-otp-code-via-sms)
- [Register a TOTP authenticator](#register-a-totp-authenticator)
- [Verify a TOTP code](#verify-a-totp-code)
- [Register a new user with SSO](#register-a-new-user-with-otp-via-sms)
- [Login a registered user](#login-a-registered-user)
- [Login a user with Direct-ID](#login-a-user-with-direct-id)
- [Identity Provider initiated SSO](#identity-provider-initiated-sso)
- [Choose an authentication method](#choose-an-authentication-method)
- [Display existing users](#display-existing-users)
- [Associate an e-mail address to a user](#associate-an-e-mail-address-to-a-user)
- [Perform Multi-Factor Authentication](#perform-multi-factor-authentication)
- [Get all user attributes](#get-all-user-attributes)
- [Set arbitrary user attributes](#set-arbitrary-user-attributes)
- [Get user groups](#get-user-groups)
- [Get user organizations](#get-user-organizations)
- [Get user handles](#get-user-handles)
- [Persist users across browser sessions](#persist-users-across-browser-sessions)
- [Storing GDPR consent](#storing-gdpr-consent)
- [Accessing the decoded user token and token container](#accessing-the-decoded-user-token-and-token-container)
- [User Analytics](#user-analytics)
### 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:
```js
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 5 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:
```js
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](#choose-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:
```js
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 5 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:
```html
<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:
```js
// optionally listen for the "otpCodeSent" event and render the OTP input field
sid.subscribe("otpCodeSent", () => {
// 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 `otpCodeSent` 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 correct, this will cause the `.id` method to resolve.
If the code is incorrect, the SDK will emit the `otpIncorrectCodeSubmitted` event and continue to listen for the `otpCodeSubmitted` event until it receives the correct OTP code. After 5 minutes without receiving the correct code it will time out and the `.id` method will not resolve.
### Register a TOTP authenticator
We are going to register a new TOTP authenticator for an authenticated user.
First we need to subscribe to some TOTP-related events:
```js
// published by the SDK when it's time to show the TOTP registration instructions to the user
sid.subscribe("totpKeyGenerated", (event) => {
// when this event triggers you should display a UI with:
// - (optional) event.uri: the registration URI for manual input in the authenticator app
// - (recommended) event.qrCode: the QR code the authenticator app will scan as base64-encoded PNG data
// - (recommended) event.recoveryCodes: recovery codes
// - (required) an input field for an OTP code, to confirm the user successfully
// registered the TOTP key in the authenticator app
// Once the user submits an OTP code you let the SDK know by publishing an event:
document.getElementById("otp").onclick = (_) => {
sid.publish("otpCodeSubmitted", document.getElementById("otp_value").value)
}
// (optional) listen for the "otpIncorrectCodeSubmitted" events
sid.subscribe("otpIncorrectCodeSubmitted", () => {
// let the user know they entered an incorrect code
})
})
```
Then we can proceed to trigger the registration flow:
```js
await user.mfa({ method: "totp" })
```
After calling the { User.mfa | .mfa} method the SlashID SDK will emit a `totpKeyGenerated` event you can subscribe to in order to
present the user with the TOTP registration UI.
Once the user sets up the authenticator app and submits an OTP code, you can publish the `otpCodeSubmitted` event to the SDK.
If the OTP code is correct, this will cause the { User.mfa | .mfa} method to resolve and update the `user` instance accordingly.
If the code is incorrect, the SDK will emit the `otpIncorrectCodeSubmitted` event and continue to listen for the `otpCodeSubmitted` event until it receives the correct OTP code. After 5 minutes without receiving the correct code it will time out and the { User.mfa | .mfa} method will not resolve.
Please note the { User.mfa | .mfa} method will only initiate a TOTP authenticator registration flow if the user has no authenticator registered, otherwise
it will automatically fall back to a TOTP verification flow.
### Verify a TOTP code
We are going to verify an authenticated user can provide correct TOTP codes.
First we need to subscribe to some TOTP-related events:
```js
// published by the SDK when it's time to show the TOTP registration instructions to the user
sid.subscribe("totpCodeRequested", () => {
// when this event triggers you should display a UI with an input field for an OTP code
// once the user submits an OTP code you let the SDK know by publishing an event:
document.getElementById("otp").onclick = (_) => {
sid.publish("otpCodeSubmitted", document.getElementById("otp_value").value)
}
// (optional) listen for the "otpIncorrectCodeSubmitted" events
sid.subscribe("otpIncorrectCodeSubmitted", () => {
// let the user know they entered an incorrect code
})
})
```
Then we can proceed to trigger the verification flow:
```js
await user.mfa({ method: "totp" })
```
After calling the { User.mfa | .mfa} method the SlashID SDK will emit a `totpCodeRequested` event you can subscribe to in order to
present the user with the TOTP code input UI.
Once the user submits an OTP code, you can publish the `otpCodeSubmitted` event to the SDK.
If the OTP code is correct, this will cause the { User.mfa | .mfa} method to resolve and update the `user` instance accordingly.
If the code is incorrect, the SDK will emit the `otpIncorrectCodeSubmitted` event and continue to listen for the `otpCodeSubmitted` event until it receives the correct OTP code. After 5 minutes without receiving the correct code it will time out and the { User.mfa | .mfa} method will not resolve.
### Register a new user with a password
Registering a user with a password is similar to registering a user with an OTP code. We need an input field to allow the user to enter the password:
```html
<label>Password:</label>
<input id="password_input" type="password" />
<button id="password_button">Submit</button>
```
Then we will set up the listeners:
```js
// listen for the "passwordSetReady" event and render the input field when this event fires
sid.subscribe("passwordSetReady", () => {
// render or enable the password input field
})
// optionally listen for the "invalidPasswordSubmitted" event
sid.subscribe("invalidPasswordSubmitted", (invalidPasswordEvent) => {
// password is invalid, let the user know
})
// let the SDK know once the password is submitted
document.getElementById("password_button").onclick = (_) => {
sid.publish("passwordSubmitted", document.getElementById("password_input").value)
}
```
Then we can proceed to trigger the authentication flow:
```js
const user = await sid.id(
MY_OID,
{
type: "email_address",
value: "test@test.com",
},
{
method: "password",
}
)
```
After calling the `.id` method to register the user with a password,
SlashID will first verify the used handle. When that is done,
SDK will emit the `passwordSetReady` event you can subscribe to in order to present the user with the password input field.
When the user submits the password, you should publish the `passwordSubmitted` event to the SDK.
If the password is valid, this will cause the `.id` method to resolve.
If the password is not valid, the SDK will emit the `invalidPasswordSubmitted` event and continue to listen for the `passwordSubmitted` event until it receives a valid password. After 5 minutes without receiving a valid password it will time out and the `.id` method will not resolve.
#### Login a registered user with a password
Authenticating an existing user with a password is similar to registering a user with a password, but it requires listening to different events in order to finish the flow:
```js
// listen for the "passwordVerifyReady" event and render the input field when this event fires
sid.subscribe("passwordVerifyReady", () => {
// render or enable the password input field
})
// optionally listen for the "incorrectPasswordSubmitted" event
sid.subscribe("incorrectPasswordSubmitted", (invalidPasswordEvent) => {
// password does not match the registered password, let the user know
})
// let the SDK know once the password is submitted
document.getElementById("password_button").onclick = (_) => {
sid.publish("passwordSubmitted", document.getElementById("password_input").value)
}
```
To recap, the login flow requires the following changes compared to the registration flow:
- listen for the `passwordVerifyReady` event instead of the `passwordSetReady` event
- listen for the `incorrectPasswordSubmitted` event instead of the `invalidPasswordSubmitted` event
After submitting the password, call to `.id` will resolve if the password is correct, or throw an error if the authentication has expired.
### Register a new user with SSO
SlashID supports OIDC and SAML SSO methods.
#### OIDC
```js
const user = await sid.id(
MY_OID,
null, // SSO should not be used with a handle
{
method: "oidc",
options: {
provider: "<PROVIDER_NAME>", // google, facebook, github...
client_id: "<GOOGLE_CLIENT_ID>",
ux_mode: "popup", // or "redirect"
},
}
)
```
#### SAML
```js
const user = await sid.id(
MY_OID,
null, // SSO should not be used with a handle
{
method: "saml",
options: {
provider_credentials_id: "<PROVIDER_CREDENTIALS_ID>",
ux_mode: "popup", // or "redirect"
},
}
)
```
Please refer to our [SSO guide](https://developer.slashid.dev/docs/access/guides/SSO/) 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:
```js
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**.
The `.id` method will return as soon as the user authenticates with Passkeys
successfully, or timeout after 5 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:
```js
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](#register-a-new-user-with-passkeys-webauthn)
and [registering new user with magic links](#register-a-new-user-with-a-magic-link) 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](https://developer.slashid.dev/docs/api/post-persons-person-id-direct-id) 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.
```js
const sid = new window.slashid.SlashID();
let user = undefined;
try {
user = await sid.getUserFromURL();
const tokenValidation = await user.validateToken();
if (!tokenValidation.valid)
// user is authenticated and the access token is valid
}catch(e) {
}
```
### Identity Provider initiated SSO
SlashID SDK will perform Identity Provider initiated SSO given a valid URL and the configuration parameters described below.
After enabling this feature, the SDK will initiate the SSO login flow if the application URL contains the query parameter in the following schema:
```
https://example.com/path?iss=social:provider:clientId
```
The `iss` parameter is mandatory, where `social:` is a prefix, `provider` is any of the supported OIDC identity provider names and `clientId` is the OIDC Client ID for the given provider.
To enable this behaviour, the corresponding configuration option needs to be passed to the constructor, along with your `oid`:
```js
const sid = new SlashID({ oid: MY_OID, identityProviderInitiatedSSOEnabled: true })
```
When your app loads, it should instantiate the SDK in the above way and then execute the following:
```js
try {
sid.getUserFromURL()
} catch (e) {}
```
This will result with a redirect to the Identity provider's login form and an immediate redirect back to the app, authenticating the user in the process.
### Recover an account
To allow a user to recover their account, use the `SlashID.recover` method. The example below shows how to trigger the recovery flow using a `password`:
```js
try {
await sid.recover(
{
{
type: "email_address",
value: "user@test.com",
},
{
method: "password",
}
}
)
} catch (e) {
console.error("Recovery failed!", e)
}
```
When this method resolves, the user can authenticate using the same handle and factor.
### Choose an authentication method
The availability of authentication methods for { 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:
```js
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:
```js
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 { 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 { 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:
```html title=page.html
...
<input id="..." type="text" list="available_identifiers" />
<datalist id="available_identifiers"></datalist>
...
```
2. Update the drop-down contents whenever you see fit:
```js title=script.js
...
// 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:
```js
// 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 { User.mfa | .mfa} method will return as soon as the user opens the link, or
timeout in 5 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:
```js
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:
```js
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:
```js
// 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 { Types.Bucket} by calling { 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
```js
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:
```js
const bucket = user.getBucket(DefaultBucketName.end_user_read_write)
const attrs = await bucket.get(["attr2", "attr3"])
// => attrs === {"attr2": 123456789, "attr3": true}
```
#### Storing attributes
```js
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 { User.getGroups | .getGroups}:
```js
const groups = user.getGroups()
// => groups === ['group-name']
```
### Get user organizations
Users must belong to at least one organization, but can have many.
The users current _organization id_ is embedded in the user token and can be retrieved with a call to { User.oid | .oid}.
The full list of user organizations can be fetched from the user using a call to { User.getOrganizations | .getOrganizations}.
```js
const orgs = await user.getOrganizations()
// => orgs === [{ id: "064ec726-...", org_name: "My org", tenant_name: "foo", managed_organizations: [] }]
```
### Get user handles
Users must have at least one handle, but can have many.
Depending on your use case this information is available in two places:
1. Handles can be retrieved using a call to { User.authentications | .authentications}. This list contains handles and authentication methods embedded in the user token. **This is not guaranteed to be a complete list of user handles**.
2. A complete list of a users handles can be fetched from the server using a call to { User.getHandles | .getHandles}
```js
const handles = await user.getHandles()
// => handles === [{ type: "email_address", value: "hello@example.com }]
```
### 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](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage):
```js
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)
```
### Storing GDPR consent
The [SlashID APIs](https://developer.slashid.dev/docs/api/post-persons-person-id-consent-gdpr) enable you to store GDPR consent for your users. This set of APIs is exposed through the methods of the `User` class.
```js
const getResult = await user.getGDPRConsent()
// => getResult.consents === []
const setResult = await user.setGDPRConsent({ consentLevels: ["necessary", "analytics"] })
// => setResult.consents === [{consent_level: "necessary", created_at: "..."}, {consent_level: "analytics", created_at: "..."}]
const addResult = await user.addGDPRConsent({ consentLevels: ["retargeting"] })
/*
=> addResult.consents === [{consent_level: "necessary", created_at: "..."}, {consent_level: "analytics", created_at: "..."}, {consent_level: "retargeting", created_at: "..."}]
*/
const removeResult = await user.removeGDPRConsent({ consentLevels: ["analytics", "retargeting"] })
// => removeResult.consents === [{consent_level: "necessary", created_at: "..."}]
const removeAllResult = await user.removeGDPRConsentAll()
// => removeAllResult.consents === []
```
### Accessing the decoded user token and token container
A `User` instance can be created with a user token or a [token container](https://developer.slashid.dev/docs/access/concepts/token_containers). You can access the claims from these tokens on the user object.
#### User token
Only the user token claims are available when creating a `User` with a user token.
```js
import { User } from "@slashid/slashid"
const user = new User("<USER_TOKEN>")
user.tokenClaims // api.UserToken
user.tokenContainerClaims // undefined
```
#### Token container
Claims for both the user token and the token container are available when creating a `User` with a token container.
```js
import { User } from "@slashid/slashid"
const user = new User("<TOKEN_CONTAINER>")
user.tokenClaims // api.UserToken
user.tokenContainerClaims // api.TokenContainer
```
### Working with anonymous persons
Anonymous persons allow for fingerprinting and collection of user data prior to login, you can read more about them in [Concept: Anonymous Persons](https://developer.slashid.dev/docs/access/concepts/anonymous).
#### Create an anonymous person
Anonymous persons can be created from an instanceo of `SlashID`. Options provided to `SlashID` during instantiation are inherited by the `AnonymousUser`.
```ts showLineNumbers
import { SlashID, AnonymousUser } from '/slashid'
const sid = new SlashID(...)
const user: AnonymousUser = sid.createAnonymousUser()
```
#### Login an anonymous user
Anonymous persons can log-in or sign-up. When an anonymous person signs-up, they are converted to a fully-fledged `User` and any data that was associated with the `AnonymousUser` is transferred to the `User`.
```ts showLineNumbers
const anonymousUser: AnonymousUser = ...
const user: BrowserUser = anonymousUser.id(identifier, factor)
```
It's highly recommended that you read the [anonymous person concept documentation](https://developer.slashid.dev/docs/access/concepts/anonymous#sign-in--sign-up) to better understand how this works and what edge cases exist.
### Using the hosted login page
As an alternative to the embedded login in single-page apps, SlashID offers a hosted login page experience that you can set up through the [SlashID Console](https://console.slashid.dev).
Once you create and customise the hosted login page, take note of the `clientId` and one of the `redirectUri` you used to set it up.
Start the flow:
```ts showLineNumbers
const sid = new SlashID(...)
sid.startHostedLoginFlow({clientId: "CLIENT_ID", redirectUri: "REDIRECT_URI"})
```
This will redirect the user to the hosted login page. Once the user authenticates successfully, they'll be redirected to `REDIRECT_URI`.
To finish the flow, call the `resolveHostedLoginFlow` method when the page identified by `REDIRECT_URI` loads:
```ts showLineNumbers
addEventListener("load", async (event) => {
// after redirect from the hosted login page
const user = await sid.resolveHostedLoginFlow()
})
```