Mise en place d'un exemple d'application

Getting Started with Adaptive Access for Native Applications

Introduction

Ce guide explique comment utiliser Adaptive Access dans une application native NodeJS à l’aide des packages Adaptive Browser SDK et Adaptive Proxy SDK.

  • Adaptive Browser SDK : diffuse le JavaScript côté client chargé de la collecte des données navigateur.
  • Adaptive Proxy SDK : fournit les classes qui gèrent les transactions d’authentification basées sur des politiques, y compris l’évaluation du risque.

Pour en savoir plus sur les entités impliquées, voir Adaptive SDK.


Prérequis


Mise en place de l’environnement

1. Créer un projet Node

npm init -y

Un fichier package.json est créé pour gérer les dépendances.

2. Installer les dépendances

npm install @ibm-verify/adaptive-browser @ibm-verify/adaptive-proxy \
            express body-parser express-session

3. Créer index.js

touch index.js

Configuration du serveur Express

Éditez index.js :

const express = require('express');
const app = express();

// Middleware JSON
app.use(express.json());

// Sessions
const session = require('express-session');
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}));

// Exposer le Browser SDK à /static/adaptive-v1.js
app.use(
  '/static/adaptive-v1.js',
  express.static(__dirname + '/node_modules/@ibm-verify/adaptive-browser/dist/adaptive-v1.min.js')
);

Ajout d’une page de connexion

1. Créer login.html

touch login.html

2. Intégrer le snippet web

Copiez le snippet fourni lors de l’on-boarding dans la balise <head> ; il chargera le Browser SDK.

Exemple de squelette :

<html>
<head>
  <!-- Collez ici votre snippet -->
</head>
<body>
  <h1>Login</h1>
  <form name="login" action="/login" method="POST">
    <label for="username">Username:</label><br>
    <input type="text" id="username" name="username"><br>
    <label for="password">Password:</label><br>
    <input type="password" id="password" name="password"><br>
    <input type="submit" value="Submit">
  </form>
</body>
</html>

❗️

Chargez le Browser SDK uniquement sur les pages login et recollection.


S’assurer que la collecte est terminée

Ajoutez le script suivant dans <head> après le snippet :

<script>
window.addEventListener('load', () => {
  const form = document.forms.login;
  form.addEventListener('submit', (e) => {
    e.preventDefault();            // Empêche l’envoi immédiat
    getSessionId().then((sid) => { // fournie par le Browser SDK
      const hidden = document.createElement('input');
      hidden.type  = 'hidden';
      hidden.name  = 'sessionId';
      hidden.value = sid;
      form.appendChild(hidden);
      form.submit();               // Soumission finale
    });
  });
});
</script>

Servir la page de connexion

Complétez index.js :

// Ressources statiques
app.get('/login', (_, res) => res.sendFile(__dirname + '/login.html'));

// Page d’accueil : affiche les tokens si authentifié
app.get('/', (req, res) => {
  if (!req.session.token) return res.redirect('/login');
  res.send(req.session.token);
});

// Lancer le serveur
app.listen(3000, () => console.log('Listening on port 3000'));

🚧

Domaine autorisé

Accédez au serveur via un domaine listé dans Allowed domains lors de l’on-boarding (sinon la collecte échouera). En développement, ajoutez une entrée 127.0.0.1 www.<ALLOWED.DOMAIN> dans /etc/hosts.


Ressource statique requise

Exposez un GIF 1×1 px à /icons/blank.gif :

app.use(
  '/icons/blank.gif',
  express.static(__dirname + '/node_modules/@ibm-verify/adaptive-browser/blank.gif')
);

Initialiser le Proxy SDK

Ajoutez à index.js (remplacez par vos valeurs) :

const Adaptive = require('@ibm-verify/adaptive-proxy');
const adaptive  = new Adaptive({
  tenantUrl:   'https://<tenant>.verify.ibm.com',
  clientId:    '<client_id>',
  clientSecret:'<client_secret>'
});

🔐

Sécurisez clientSecret en production (vault/env var).


Authentifier un utilisateur

Ajoutez à index.js avant app.listen :

const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));

let identitySourceId; // mis en cache

app.post('/login', async (req, res) => {
  const { username, password, sessionId } = req.body;

  const context = {
    sessionId,
    userAgent: req.headers['user-agent'],
    ipAddress: req.ip
  };

  try {
    // 1. Pré-évaluation ( politique + facteurs autorisés )
    const assess = await adaptive.assessPolicy(context);
    if (assess.status === 'deny')
      return res.status(403).send({ error: 'Denied' });

    // 2. Recherche de la source (une seule fois)
    if (!identitySourceId) {
      const sources = await adaptive.lookupIdentitySources(
        context, assess.transactionId, 'Cloud Directory'
      );
      identitySourceId = sources[0].id;
    }

    // 3. Vérifier le mot de passe
    const result = await adaptive.evaluatePassword(
      context, assess.transactionId, identitySourceId, username, password
    );

    handleEvaluateResult(req, res, context, result);

  } catch (err) {
    console.error(err);
    const msg = err.response?.data?.messageDescription || err.message;
    res.status(403).send({ error: msg });
  }
});

// Gestion centralisée des réponses
function handleEvaluateResult(req, res, context, result) {

  if (result.status === 'deny')
    return res.status(403).send({ error: 'Denied' });

  if (result.status === 'allow') {
    req.session.token = result.token;
    return res.redirect('/');
  }

  if (result.status === 'requires') {
    const fact = result.enrolledFactors.find(f => f.type === 'emailotp');
    if (!fact)
      return res.status(500).send({ error: 'Email OTP not available' });

    req.session.transactionId = result.transactionId;
    req.session.currentFactor = 'emailotp';

    adaptive.generateEmailOTP(context, result.transactionId, fact.id)
      .then(() => res.sendFile(__dirname + '/otp.html'))
      .catch(e => res.status(500).send({ error: e.message }));
  }
}

Page OTP (otp.html)

<html>
<body>
  <h1>Submit OTP</h1>
  <form action="/otp" method="POST">
    <label for="otp">OTP :</label><br>
    <input type="text" id="otp" name="otp"><br>
    <input type="submit" value="Submit">
  </form>
</body>
</html>

Ajoutez la route :

app.post('/otp', async (req, res) => {
  const otp          = req.body.otp;
  const { sessionId, transactionId } = req.session;

  const context = {
    sessionId,
    userAgent: req.headers['user-agent'],
    ipAddress: req.ip
  };

  try {
    const result = await adaptive.evaluateEmailOTP(context, transactionId, otp);

    if (result.status === 'deny')
      return res.status(403).send({ error: 'Denied' });

    if (result.status === 'allow') {
      req.session.token = result.token;
      return res.redirect('/');
    }

  } catch (e) {
    const msg = e.response?.data?.messageDescription || e.message;
    res.status(403).send({ error: msg });
  }
});

Lancer et tester

node index.js
  1. Allez sur http://www.<ALLOWED.DOMAIN>:3000/
  2. Page de login → saisissez username/password
  3. Recevez et saisissez l’OTP e-mail
  4. Vous êtes redirigé sur / : les jetons s’affichent.

Vous avez configuré avec succès Adaptive Access pour une application native ! 🎉