Skip to content

Setup Anonymous token provider with Headless Form

Linh Hoang edited this page Sep 10, 2024 · 1 revision

Configure Form Headless with Anonymous token provider

Headless Form API only allow API usage for authenticated user. This setup configured a sample anonymous token provider to generate a token for anonymous user based on their session ID to use for submission.

This sample modify the default JSSDK music festival sample site. Reference: https://github.com/episerver/content-headless-form-js-sdk/tree/develop/samples

Step 1: Setup Token Provider

Setup a token provider for users/visitors on the site that provides tokens to authorize API access.

Below is a sample builtin controller to generate token: (This should be configured with other token provider or custom token service)

    [Route("api/[controller]")]
    [ApiController]
    public class JwtTokenController : ControllerBase
    {
        private readonly string SecretKey = "6e13c68325f0379c2a6b1277e4eb9b97";

        public JwtTokenController() { }

        [HttpGet("GetJwtToken")]
        public JsonResult GetJwtToken(string username)
        {
            return new JsonResult(GenerateToken(new() { Username = username, Role = "user"}));
        }
        // To generate token
        private string GenerateToken(UserModel user)
        {
            var encryptionKey = Encoding.UTF8.GetBytes(SecretKey);

            var claims = new[]
            {
                new Claim("name", user.Username),
                new Claim("role", user.Role)
            };

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Issuer = "tsng",
                Subject = new ClaimsIdentity(claims),
                Expires = DateTime.UtcNow.AddDays(1),
            };

            var tokenHandler = new JwtSecurityTokenHandler();

            var token = tokenHandler.CreateJwtSecurityToken(tokenDescriptor);

            return tokenHandler.WriteToken(token);
        }

        private class UserModel
        {
            public string Username { get; set; }
            public string Role { get; set; }
        }

    }

Step 2: Setup Client Site to Retrieve Token Provider

Update the site to use the configured token for form submission.

Sample code changed to JSSDK music festival site to interact with our builtin sample token provider:

  • Configure AuthProvider to get token
    • AuthProvider.js
const jose = require('jose');

class AuthProvider {
    // Secret key used to sign the JWT token
    constructor() {}

    generateSessionId() {
        const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        const length = 20; // You can adjust the length of the session ID as needed
        let sessionId = '';

        for (let i = 0; i < length; i++) {
            const randomIndex = Math.floor(Math.random() * characters.length);
            sessionId += characters[randomIndex];
        }

        return `anonymous_${sessionId}`;
    }

    async generateJwtToken(username, authenticated) {
        if (!username) {
            username = this.generateSessionId();
            authenticated = false;
        } else {
            authenticated = authenticated ?? true;
        }

        const payload = {
            username: username,
            authenticated: authenticated,
            exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24)  // Token expiration time (1 day)
        };

        const token = await fetch(`http://domain:port/api/JwtToken/GetJwtToken?username=${username}`);
        sessionStorage.setItem('user', JSON.stringify({ access_token: await token.json(), ...payload }));
        return token;
    }

    getUser() {
        return new Promise((resolve, reject) => {
            const user = sessionStorage.getItem('user');

            user ? resolve(JSON.parse(user)) :
                this.generateJwtToken(null).then(data => resolve(JSON.parse(sessionStorage.getItem('user'))));
        });
    }

    signinRedirect(args) {
        this.generateJwtToken("authenticated_user");
        window.location.href = args.state;
    }

    signoutRedirect() {
        this.generateJwtToken(null);
        window.location.reload();
    }

    refreshAccessToken() {
        this.getUser().then(user => {
            return this.generateJwtToken(user.username, user.authenticated);
        });
    }
}

export default AuthProvider;
  • AuthService.js
import AuthProvider from './authProvider';

class AuthService {
  constructor() {
    this.authProvider = new AuthProvider();
  }

  getUser() {
    return new Promise( (resolve, reject) => {
      this.authProvider.getUser().then(user => {
        console.log(`JWT Bearer token for ${user.username}: ${user.access_token}`)
        resolve({...user, expired: Math.floor(Date.now() / 1000) > user.exp}) 
      })
    })
  }

  login() {
    const args = {
      state: window.location.href,
    };

    return this.authProvider.signinRedirect(args);
  }

  logout() {
    return this.authProvider.signoutRedirect();
  }

  getAccessToken() {
    return this.authProvider.getUser();
  }

  refreshAccessToken() {
    return this.authProvider.generateJwtToken()
  }
}

const authService = new AuthService();

export default authService;
  • Configure Header.tsx to pass token with API request
        authService.getUser().then((user: any) => {
            if(user && user.expired) {
                authService.refreshAccessToken().then((_: any) => {
                    authService.getUser().then((_user: any) => { user = _user })
                })
            }

            if (user && !user.expired) {
                setIsLoggedIn(user.authenticated);
                setUsername(user.username || "");
                formCache.set<string>(FormConstants.FormAccessToken, user.access_token);
            }
        });

Step 3: Setup Server Site to use Form Headless API with our token provider

Modify startup.cs file and add these lines inside and at the end of the Configure method:

// Register the Optimizely Headless Form API Services
services.AddOptimizelyFormsService(options =>
{
    options.EnableOpenApiDocumentation = true;
    options.FormCorsPolicy = new FormCorsPolicy
    {
        AllowOrigins = new string[] { https://domain.com }, //Enter '*' to allow any origins, multiple origins separate by comma
        AllowCredentials = true
    };
    options.OpenIDConnectClients.Add(new()
    {
        Authority = ClientEndpoint,
        EncryptionKeys = new List<SecurityKey>{ new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)) }
        SigningKeys = new List<SecurityKey>{ new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)) }
    });
});