-
Notifications
You must be signed in to change notification settings - Fork 1
Setup Anonymous token provider with Headless Form
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
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; }
}
}
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);
}
});
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)) }
});
});