React UI kit authentication

For single-sign-on (SSO) and seamless authentication between your app and Weavy, the React UI kit should act on behalf of your authenticated user. This is known as user-to-server communication and requires an access_token for API calls to the Weavy backend.

Token factory

When adding the <WeavyProvider> component, you need to specify a tokenFactory. The token factory should be an async function which accepts a single boolean argument and is expected to return an access_token for your authenticated user:

async function getAccessToken(refresh) {
  // typically implemented as an API call to *your* backend
  // which returns an access_token for the authenticated user
}

When the <WeavyProvider> is first initialized, your function will be called with refresh=false. The <WeavyProvider> will use the access_token you return as long as it is valid, but if the token expires or is revoked your function will be called again with refresh=true. This allows your code to clear the invalid token from your application’s storage and request a new token from the Weavy backend.

Implementing the token factory

You will typically implement the tokenFactory function with an API call to your backend as in the following example:

import React, { useCallback, useEffect, useState } from 'react';
import { WeavyClient, WeavyProvider } from '@weavy/uikit-react';

function App() {
   const [weavyClient, setWeavyClient] = useState(null);
   const [user, setUser] = useState(null);
   const [loading, setLoading] = useState(true);

   const getToken = useCallback(async (refresh) => {
       // fetch access_token for the authenticated user with an api call to your application backend
       let response = await fetch(`/token?&refresh=${refresh}`);
       let json = await response.json();
       return json.access_token

   }, []);

   useEffect(() => {
       if(user){
           let client = new WeavyClient({ url: '{WEAVY_BACKEND_URL}', tokenFactory: getToken });
           setWeavyClient(client);
       }        
   }, [getToken, user]);

   useEffect(() => {
       async function fetchUser() {
           // get signed in user from server if available
           let response = await fetch('/auth');
           let user = await response.text();
           setUser(user);
           setLoading(false);
       }
       fetchUser();
   }, []);

   const handleLogin = (user) => {
       setUser(user)
   }

   const handleLogout = async () => {
       // call /logout on server to clear session cookie
       await fetch('/logout');

       // destroy the Weavy client instance
       weavyClient.destroy();

       // Reset state variables. When client property on WeavyProvider is set to null, the internal query cache is also reset. Make sure to do this when you sign out a user.
       setWeavyClient(null);
       setUser(null);
   }

   return (
       <div className="App">
           {!user &&
               <!-- A login component which calls /login on the server with username and password. Check server implementation below -->
               <Login onAuthenticated={handleLogin} />
           }
           
           {user && 
               <div>
                   <button className='btn btn-primary' onClick={handleLogout}>Log out</button>
                   <WeavyProvider client={weavyClient}>
                       <!-- Weavy components goes here -->
                   </WeavyProvider>
               </div>
           }           
       </div>
   )
}

export default App;

Your backend then requests an access_token from the Weavy backend with a server-to-server request as explained in the API authentication article. In the example below we show what an implementation of the /token endpoint could look like if you have a Node backend. As seen in the example, we recommend you store and reuse tokens. This avoid unnecessary roundtrips and reduces your application's attack surface.

import express from 'express';
import fetch from 'node-fetch'
import config from 'config';
import cookieSession from 'cookie-session';

const app = express();
let _tokens = [];
const apiKey= config.get("apiKey");

app.use(express.json());
app.use(cookieSession({
    name: 'session',
    secret: 'secret_key'
}));

app.post('/login', (req, res) => {
    let username = req.body.username;
    let password = req.body.password;

    // validate user here...
    let validUser = true; // validate user...
    let message = "OK";
    if(validUser){
        req.session.user = `${username}`;        
    } else{
        message = "ERROR"
    }
    
    res.end(message)

});

// get authenticated user if available
app.get('/auth', (req, res) => {    
    res.end(req.session.user);
});

// sign out user
app.get('/logout', (req, res) => {    
    req.session = null;
    res.end("Logged out...");
});

// get token from Weavy server
app.get("/token", async (req, res) => {
     let username = req.session.user; // get user from session
    
    if((!req.query.refresh || req.query.refresh === "false") && _tokens.find((t) => t.username === username)){        
        res.json({ access_token: _tokens.find((t) => t.username === username).access_token});        
    } else{
        let response = await fetch(`${config.get("backend")}/api/users/${username}/tokens`, {
            method: 'POST',
            headers: {
                'content-type': 'application/json',
                'Authorization': `Bearer ${apiKey}`
            },
            body: JSON.stringify({ name: username })
        });
        
        if(response.ok){
            let data = await response.json();                
            _tokens = [..._tokens.filter((t) => t.username !== username), { username: username, access_token: data.access_token}];    
            res.json(data);            
        } else{
            res.json({ message: "Could not get access token from server!" })            
        }
    }    
});

app.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
});
Weavy Docs