Authentication

Snippet for authentication flow (Oauth2)

Each time the SDK refreshes the accessToken the freshTokensCallback is called with the response. You can store this data in localStorage or any other persistant data store. When you restart your application, you can check the data store for a refreshToken and use that to authenticate with the SDK.

import { createOAuth2Client } from "@extrahorizon/javascript-sdk";

const exh = createOAuth2Client({
  host: "",
  clientId: "",
  freshTokensCallback: (tokenData) => {
    localStorage.setItem("refreshToken", tokenData.refreshToken);
  },
});

try {
  const refreshToken = await localStorage.getItem("refreshToken");

  if (refreshToken) {
    await exh.auth.authenticate({
      refreshToken,
    });
  } else {
    // redirect to /login
  }
} catch (error) {
  localStorage.removeItem("refreshToken");
  // redirect to /login
}

Snippet for authentication flow (Oauth1)

You need to capture the response from the authenticate function when logging in with email / password so that subsequent SDK initializations such as app restarts can use the key / secret combination stored in persistent data storage to authenticate the current user.

import { createOAuth1Client } from "@extrahorizon/javascript-sdk";

const exh = createOAuth1Client({
  host: "https://api.dev.exh-sandbox.extrahorizon.io",
  consumerKey: "",
  consumerSecret: "",
});

try {
  const tokenData = await localStorage.getItem("tokenData");

  if (tokenData) {
    await exh.auth.authenticate({
      token: tokenData.key,
      tokenSecret: tokenData.secret,
    });
  } else {
    // redirect to /login
    const result = await exh.auth.authenticate({
      email: "",
      password: "",
    });
    localStorage.setItem("tokenData", result);
  }
} catch (error) {
  localStorage.removeItem("tokenData");
  // redirect to /login
}

Proxy client

The package export a client you can use in combination with a proxy service. The client will throw a typed error in case you need to redirect to the login page.

import { createProxyClient } from "@extrahorizon/javascript-sdk";

const loginPageUrl = "https://yourDomain.com/login";

(async () => {
  try {
    const exh = createProxyClient({ host: "api.dev.exh-sandbox.extrahorizon.io" });
    await exh.users.me();
  } catch (error) {
    if (
      error instanceof UserNotAuthenticatedError ||
      error instanceof OauthTokenError
    ) {
      redirectToUrl(`${loginPageUrl}/?redirect=${window.location.url}`);
    }
  }
})();

Local setup

If you want to use the proxy sdk locally, you need to make some changes to your local setup.

  • Add 127.0.0.1 local.yourdomain.com to your /etc/hosts file (or if you are using Windows c:\Windows\System32\Drivers\etc\hosts)

  • Start your server with https enabled.

    • For Mac/Linux, this can be done by running HTTPS=true yarn start.

    • For Windows, you have to add HTTPS=true to your user environment. Once the variable has been set, run yarn start.

  • Open your browser https://local.yourdomain.com:3000/ and skip the security warning.

Snippet for stored credentials

When you already use the exh/cli tool, you can use this snippet to initialize. More info: https://docs.extrahorizon.com/cli/setup/credentials

import fs from "fs";
import path from "path";
import {
  parseStoredCredentials,
  createOAuth1Client,
} from "@extrahorizon/javascript-sdk";

const EXH_CONFIG_FILE = path.join(process.env.HOME, "/.exh/credentials");

const readFile = () => {
  try {
    return fs.readFileSync(EXH_CONFIG_FILE, "utf-8");
  } catch (err) {
    throw new Error(
      `Failed to open credentials file. Make sure they are correctly specified in ${EXH_CONFIG_FILE}`
    );
  }
};

try {
  const credentials = parseStoredCredentials(readFile());
  const exh = createOAuth1Client({
    consumerKey: credentials.API_OAUTH_CONSUMER_KEY,
    consumerSecret: credentials.API_OAUTH_CONSUMER_SECRET,
    host: credentials.API_HOST,
  });
  await exh.auth.authenticate({
    token: credentials.API_OAUTH_TOKEN,
    tokenSecret: credentials.API_OAUTH_TOKEN_SECRET,
  });
} catch (error) {
  console.log(error);
}

Other examples

OAuth1

Token authentication with optional skip

The skipTokenCheck saves ~300ms by skipping validation on your token and tokenSecret.

import { createOAuth1Client } from "@extrahorion/javascript-sdk";

const exh = createOAuth1Client({
  host: "sandbox.extrahorizon.io",
  consumerKey: "",
  consumerSecret: "",
});

await exh.auth.authenticate({
  token: "",
  tokenSecret: "",
  skipTokenCheck: true,
});

Email authentication

import { createOAuth1Client } from "@extrahorizon/javascript-sdk";

const exh = createOAuth1Client({
  host: "sandbox.extrahorizon.io",
  consumerKey: "",
  consumerSecret: "",
});

await exh.auth.authenticate({
  email: "",
  password: "",
});

OAuth2

Password Grant flow

import { createOAuth2Client } from "@extrahorizon/javascript-sdk";

const exh = createOAuth2Client({
  host: "",
  clientId: "",
});

await exh.auth.authenticate({
  password: "",
  username: "",
});

Authorization Code Grant flow with callback

  • Generating an Authorization Code is out of scope for this snippet, but generally:

    • Your application has a login/authorization page

    • It allows the user to login to your application

    • Shows the information about the (other, 3rd party) application requesting access

    • After consent to give access to the user its account, redirects the user to the application

  • The (3rd party) application then receives the Authorization Code in the query parameters

  • Capture the query params on the redirect uri

  • Authenticate with the code query param

import { createOAuth2Client } from "@extrahorizon/javascript-sdk";

const exh = createOAuth2Client({
  host: "",
  clientId: "",
});

await exh.auth.authenticate({
  code: "",
});

Refresh Token Grant flow

import { createOAuth2Client } from "@extrahorizon/javascript-sdk";

const exh = createOAuth2Client({
  host: "",
  clientId: "",
});

await exh.auth.authenticate({
  refreshToken: "",
});

Password Grant flow with two-step MFA in try / catch

import {
  createOAuth2Client,
  MfaRequiredError,
} from "@extrahorizon/javascript-sdk";

const exh = createOAuth2Client({
  host: "",
  clientId: "",
});

try {
  await exh.auth.authenticate({
    password: "",
    username: "",
  });
} catch (error) {
  if (error instanceof MfaRequiredError) {
    const { mfa } = error;

    // Your logic to request which method the user want to use in case of multiple methods
    const methodId = mfa.methods[0].id;

    await exh.auth.confirmMfa({
      token: mfa.token,
      methodId,
      code: "", // code from ie. Google Authenticator
    });
  }
}

Confidential Applications

If you are using a confidential application in combination with React-Native. The SDK will add btoa function to your global scope. See https://github.com/ExtraHorizon/javascript-sdk/issues/446

const exh = createClient({
  host: "https://api.dev.exh-sandbox.extrahorizon.io",
  clientId: "",
  clientSecret: "",
});

Creating applications

Example

If you want to create an application can you use generic to determine the correct application and application version type.

ie. creating an OAuth1 application with a version.

// Will return OAuth1Application type
const app = await exh.auth.applications.create({
  type: "oauth1",
  name: "test",
  description: "test",
});

// Will return OAuth1ApplicationVersion type
const version = await exh.auth.applications.createVersion<typeof app>(app.id, {
  name: "1.0.0",
});

Typeguards

If you need a typeguard, you can use the following snippets.

import {
  Application,
  ApplicationVersion,
  OAuth1Application,
  OAuth1ApplicationVersion,
} from "@extrahorizon/javascript-sdk";

function isOAuth1Version(
  version: ApplicationVersion
): version is OAuth1ApplicationVersion {
  return `consumerKey` in version;
}

function isOAuth1(app: Application): app is OAuth1Application {
  return !("redirectUris" in app);
}

const { data: apps } = await exh.auth.applications.get();
apps.filter(isOAuth1).forEach((app) => {
  // app will have type OAuth1Application
});

Last updated