NodeJS Apptoken - Invalid app token hash

Greetings! We are recently trying to implement AppToken authorisation for our NextJS application . Following the workflow, if I copy and paste the step 3 setup code, I can get a successful privileged session creation. However, there our admin secret is used to create the ‘unprivileged’ session to fetch the appToken.

Trying to create the unprivileged session via startWidgetSession and using that session to compute the tokenHash as described in preceding steps to fetch the token I get errors of invalid app token hash.

The app token was created for us by an account administrator as type USER with Sha256.

Below the 3 approaches tried - both the first approaches produce the same result of invalid hash. The last is the approach copy/pasted from the workflow which works but needs the admin secret. Can you advise is there something wrong with the way we create the hash or other steps in NodeJS?

Thank you!

const crypto = require('crypto')
const kaltura = require('kaltura-client')

export async function getAppTokenSession(): Promise<any> {
  const config = new kaltura.Configuration()
  config.serviceUrl = 'https://www.kaltura.com'
  const client = new kaltura.Client(config)

  const widgetId = `_${process.env.NEXT_PUBLIC_KALTURA_PARTNER_ID}`
  const expiry = 86400
  //   console.log('Entered getAppTokenSession()')
  //   kaltura.services.session
  //     .startWidgetSession(widgetId, expiry)
  //     .execute(client)
  //     .then((success) => {
  //       console.log('success', success)
  //       client.setKs(success.ks)
  //       //const shasum = crypto.createHash('sha256')
  //       //shasum.update(client.ks + process.env.KALTURA_APP_TOKEN)
  //       // const hash = shasum.digest('hex')
  //       const hash = crypto
  //         .createHash('sha256')
  //         .update(client.ks + process.env.KALTURA_APP_TOKEN)
  //         .digest('hex')
  //       console.log('**HASH**', hash)

  //       const id = process.env.KALTURA_APP_TOKEN_ID
  //       const tokenHash = hash
  //       const userId = process.env.KALTURA_USER
  //       const type = kaltura.enums.SessionType.USER
  //       const appTokenSessionExpiry = 0
  //       const sessionPrivileges = ''

  //       kaltura.services.appToken
  //         .startSession(
  //           id,
  //           tokenHash,
  //           userId,
  //           type,
  //           appTokenSessionExpiry,
  //           sessionPrivileges
  //         )
  //         .execute(client)
  //         .then((result) => {
  //           console.log('got app token without C&P Step 3 workflow')
  //           console.log(result)
  //         })
  //     })

  const widgetSession = await kaltura.services.session
    .startWidgetSession(widgetId, expiry)
    .execute(client)
  console.log('widgetSession', widgetSession)
  const shasum = crypto.createHash('sha256')
  client.setKs(widgetSession.ks)
  shasum.update(client.ks + process.env.KALTURA_APP_TOKEN)
  const hash = shasum.digest('hex')
  console.log('**HASH**', hash)

  const id = process.env.KALTURA_APP_TOKEN_ID
  const tokenHash = hash
  const userId = process.env.KALTURA_USER
  const type = kaltura.enums.SessionType.USER
  const appTokenSessionExpiry = 0
  const sessionPrivileges = ''

  kaltura.services.appToken
    .startSession(
      id,
      tokenHash,
      userId,
      type,
      appTokenSessionExpiry,
      sessionPrivileges
    )
    .execute(client)
    .then((result) => {
      console.log('got app token without C&P Step 3 workflow')
      console.log(result)
    })

  // THIS WORKS BUT USES SECRET TO START UNPRIVILEGED SESSION
  //   kaltura.services.session
  //     .start(
  //       process.env.KALTURA_ADMIN_SECRET,
  //       process.env.KALTURA_USER,
  //       kaltura.enums.SessionType.ADMIN,
  //       1936261
  //     )
  //     .completion((success, ks) => {
  //       if (!success) {
  //         throw new Error(ks.message)
  //       }
  //       const shasum = crypto.createHash('sha256')

  //       client.ks = ks
  //       shasum.update(client.ks + process.env.KALTURA_APP_TOKEN)
  //       const hash = shasum.digest('hex')

  //       client.setKs(ks)
  //       const id = process.env.KALTURA_APP_TOKEN_ID
  //       const tokenHash = hash
  //       const userId = process.env.KALTURA_USER
  //       const type = kaltura.enums.SessionType.ADMIN
  //       const expiry = 0
  //       const sessionPrivileges = ''

  //       kaltura.services.appToken
  //         .startSession(id, tokenHash, userId, type, expiry, sessionPrivileges)
  //         .execute(client)
  //         .then((result) => {
  //           console.log('got app token woo!')
  //           console.log(result)
  //         })
  //     })
  //     .execute(client)
}

Hi @ShaneSAP .

Below is a working example.

const crypto = require('crypto')
const kaltura = require('kaltura-client')

function getAppTokenSession() {
  const config = new kaltura.Configuration()
  config.serviceUrl = 'https://www.kaltura.com'
  const client = new kaltura.Client(config)

  const widgetId = `_${process.env.NEXT_PUBLIC_KALTURA_PARTNER_ID}`
  console.log(widgetId);
  const expiry = 86400
  const widgetSession = kaltura.services.session
    .startWidgetSession(widgetId, expiry)
    .execute(client)
    .then(result => {
	client.setKs(result.ks);
	const shasum = crypto.createHash('sha256')
	shasum.update(client.getKs() + process.env.KALTURA_APP_TOKEN)
	const tokenHash = shasum.digest('hex')

	const id = process.env.KALTURA_APP_TOKEN_ID
	const userId = process.env.KALTURA_USER
	const type = kaltura.enums.SessionType.USER
	const appTokenSessionExpiry = 86400
	const sessionPrivileges = ''

	kaltura.services.appToken
	    .startSession(
		id,
		tokenHash,
		userId,
		type,
		appTokenSessionExpiry,
		sessionPrivileges
	    )
	    .execute(client)
	    .then((result) => {
		console.log('got app token without C&P Step 3 workflow')
		console.log(result)
                // set new KS on the client.
		client.setKs(result.ks);
                // make some requests, media.list() in this case:
		kaltura.services.media.listAction()
		.execute(client)
		.then(result => {
		    console.log(result);
		});
	    })
    })

}

getAppTokenSession();

Cheers,

Hi Jess, thank you for the info. The approach you gave worked, but then we tried to break up the ‘doom pyramid’ to separate functions, we started to get inconsistent results.

For context, this service would be called by both serverside in getStaticProps by some pages, and some client side calls via an api route in NextJS.

import { IKalturaVideoMetadataAPI } from '../../types/KalturaAPI/getMedia'

const crypto = require('crypto')
const kaltura = require('kaltura-client')

export async function getVideoMetadataWithAppTokenBackend(
  entryId: string
): Promise<IKalturaVideoMetadataAPI> {
  const config = new kaltura.Configuration()
  config.serviceUrl = process.env.KALTURA_SERVICE_URL
  const client = new kaltura.Client(config)
  const unPrivSession = await startUnprivilegedSession(client)
  client.setKs(unPrivSession.ks)
  const tokenHash = computeKalturaHash(client.getKs())
  //const privSession = await startPrivilegedSession(client, tokenHash)
  //client.setKs(privSession.ks)

  return new Promise((resolve, reject) => {
    kaltura.services.media
      .get(entryId)
      .execute(client)
      .then((result: any) => {
        //kaltura.services.session.end().execute(client)
        const metaData: IKalturaVideoMetadataAPI = {
          id: result.id,
          name: result.name,
          duration: result.duration,
          description: result.description,
          creatorId: '',
          createdAt: result.createdAt,
          views: result.views,
        }
        resolve(metaData)
      })
      .catch(reject)
  })
}

async function startUnprivilegedSession(client: any) {
  const widgetId = `_${process.env.NEXT_PUBLIC_KALTURA_PARTNER_ID}`
  const expiry = 86400
  return kaltura.services.session
    .startWidgetSession(widgetId, expiry)
    .execute(client)
}

function computeKalturaHash(ks: string) {
  const shasum = crypto.createHash('sha256')
  shasum.update(ks + process.env.KALTURA_APP_TOKEN)
  const hash = shasum.digest('hex')
  return hash
}

async function startPrivilegedSession(client: any, tokenHash: any) {
  console.log('in privileged session', tokenHash)
  const id = process.env.KALTURA_APP_TOKEN_ID
  const userId = process.env.KALTURA_USER
  const type = kaltura.enums.SessionType.USER
  const appTokenSessionExpiry = 86400
  const sessionPrivileges = ''
  return kaltura.services.appToken
    .startSession(
      id,
      tokenHash,
      userId,
      type,
      appTokenSessionExpiry,
      sessionPrivileges
    )
    .execute(client)
}

This code (without the commented lines) seemed was working initially, but I started to see 404 results when I reduced the expiry time for sessions to 60. There was inconsistent results at 400 expiry time.

We also experimented with ending the session manually after we got the result we wanted, as you can see from the commented //kaltura.services.session.end().execute(client), but after this , we could no longer successfully fetch data with the privileged session, instead getting these errors:

error - Error: {"code":"INVALID_KS","message":"Invalid KS \"LOGOUT\", Error \"-1,INVALID_STR\"","objectType":"KalturaAPIException","args":{"KSID":"LOGOUT","ERR_CODE":"-1","ERR_DESC":"INVALID_STR"}}

Is it possible we killed our AppToken itself somehow by trying to end our session this way?

And another really strange thing, after all of this, when we comment out the fetching of the privileged session //const privSession = await startPrivilegedSession(client, tokenHash) //client.setKs(privSession.ks) as you see in the snippet above… the fetching of the metadata appears to work, seemingly on the unprivileged session alone (or possibly from pre-existing privileged session that is yet to expire?)

Any support is much appreciated! Thank you.