Custom node credentials - preAuthentication method doesn't execute http request

Describe the problem/error/question

I am trying to create the connection for Opera Cloud.

Documentation:

It has an OAuth Token request that returns:

{
    "access_token": "xyz",
    "expires_in": 3600,
    "token_type": "Bearer"
}

The POST request needs:

  • x-www-form-urlencoded body
  • Basic Auth with CLIENT_ID & CLIENT_SECRET
  • x-app-key in Headers
  • username, password, grant_type: ‘password’ in body

What is the error message (if any)?

I noticed the httpRequest is not performed in preAuthentication. If I console log before & after that http call I can see the first log only.

I am trying to save the access_token in sessionToken, which has property expirable: true and use authenticate to pass the parameters needed in headers (x-app-key, x-hotelid, Authorization) for the authentication.

Similar to what was recommended in this thread:

The problem is that sessionToken remains empty and the httpRequest inside preAuthentication is not performing. The same request with same parameters works in Postman.

Please share your workflow

This is my credentials.ts file:

import {
	IAuthenticateGeneric,
	ICredentialDataDecryptedObject,
	ICredentialTestRequest,
	ICredentialType,
	IHttpRequestHelper,
	INodeProperties,
} from 'n8n-workflow';

export class OperaCloudOAuth2Api implements ICredentialType {

	name = 'operaCloudOAuth2Api';
	displayName = 'Opera Cloud OAuth2 API';

	properties: INodeProperties[] = [
		{
			displayName: 'Username',
			name: 'username',
			type: 'string',
			default: '',
			required: true,
		},
		{
			displayName: 'Password',
			name: 'password',
			type: 'string',
			typeOptions: {
				password: true,
			},
			default: '',
			required: true,
		},
		{
			displayName: 'Client ID',
			name: 'clientId',
			type: 'string',
			default: '',
			required: true,
		},
		{
			displayName: 'Client Secret',
			name: 'clientSecret',
			type: 'string',
			typeOptions: {
				password: true,
			},
			default: '',
			required: true,
		},
		{
			displayName: 'Gateway URL',
			name: 'gatewayUrl',
			type: 'string',
			default: '',
			required: true,
		},
		{
			displayName: 'App Key',
			name: 'appKey',
			type: 'string',
			typeOptions: {
				password: true,
			},
			default: '',
			required: true
		},
		{
			displayName: 'Session Token',
			name: 'sessionToken',
			type: 'hidden',
			typeOptions: {
				expirable: true,
			},
			default: '',
		},
		{
			displayName: 'Hotel ID',
			name: 'hotelId',
			type: 'string',
			required: true,
			default: '',
		},

	];

	async preAuthentication(this: IHttpRequestHelper, credentials: ICredentialDataDecryptedObject) {
		// make reques to get access token

		const basicAuthKey = Buffer.from(
			`${credentials.clientId}:${credentials.clientSecret}`,
		).toString('base64');

		const url = `${credentials.gatewayUrl}/oauth/v1/tokens`;
		const { access_token } = (await this.helpers.httpRequest({
			method: 'POST',
			url: url,
			headers: {
                Authorization: `Basic ${basicAuthKey}`,
				        "x-app-key": credentials.appKey
			},
			body: {
				username: credentials.username,
				password: credentials.password,
				grant_type: "password"
			},
		})) as { access_token: string };
		return { sessionToken: access_token };
	}

	authenticate: IAuthenticateGeneric = {
		type: 'generic',
		properties: {
			headers: {
				'x-app-key': '={{$credentials.appKey}}',
				'x-hotelid': '={{$credentials.hotelId}}',
				Authorization: '=Bearer {{$credentials.sessionToken}}'
			},
		},
	};

	test: ICredentialTestRequest = {
		request: {
			baseURL: '',
			url: '',
		}}

}

Can someone point what I’m doing wrong here or what might be the reason for this problem?
Let me know if you need more information and I’ll gladly provide.

Thank you!

Information on your n8n setup

  • n8n version: 0.233.1
  • Running n8n via: npm
  • Operating system: MAC
1 Like

I discovered that it works if I use this.helpers.request and test it in node.ts file.

Unfortunately I can’t use this.helpers.request inside preAuthentication as it accepts first parameter this: IHttpRequestHelper and request is part of RequestHelperFunctions.

Any idea why this.helpers.httpRequest doesn’t have same execution result as this.helpers.request?

Thank you!

Hey @alexnemes,

The credential file itself looks fine to me, Is your node using the preferred this.helpers.requestWithAuthentication option?

Hey @Jon the problem is that sessionToken is empty after I save the connection. If I print the credentials when I run an operation, sessionToken is empty. So it has to be some problem in the credentials file I assume

Hey @alexnemes,

I can’t see an issue in your credential file it looks like the other ones I have created using the same option, It could be that the API calls are incorrect I have not looked at the API you are using but I take it you have already done an output on that to see what it is returning.

Are you able to share the full code for your node?

@Jon This is the code for my node.ts file, currently only 1 operation:

import { IExecuteFunctions } from 'n8n-core';

import {
	IDataObject,
	IHttpRequestOptions,
	INodeExecutionData,
	INodeType,
	INodeTypeDescription,

} from 'n8n-workflow';

import { ReservationOperations, ReservationFields } from './Descriptions/ReservationDescription';

import { mapCustomPropertiesToObject } from './GenericFunctions';


export class OperaCloud implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'OperaCloud',
		name: 'operaCloud',
		icon: 'file:oracle-hospitality.svg',
		group: ['transform'],
		version: 1,
		subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
		description: 'Consume OperaCloud API',
		defaults: {
			name: 'OperaCloud',
		},
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'operaCloudOAuth2Api',
				required: true,
			},
		],

		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				options: [

					{
						name: 'Reservation',
						value: 'reservation',
					},

				],
				default: 'reservation',
				noDataExpression: true,
				required: true,
				description: 'Select resource',
			},

			// RESERVATION
			...ReservationOperations,
			...ReservationFields,

		],
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		const items = this.getInputData();
		let responseData;
		const returnData = [];
		const resource = this.getNodeParameter('resource', 0) as string;
		const operation = this.getNodeParameter('operation', 0) as string;

		const credentials = await this.getCredentials('operaCloudOAuth2Api');

		for (let i = 0; i < items.length; i++) {

			// getHotelReservations
			if (resource === 'reservation') {
				if (operation === 'getHotelReservations') {
					const customParamsUI = this.getNodeParameter('customParamsUI', i) as IDataObject;
					const customParamsArr = customParamsUI.parameters as Array<any>;
					const qs = {} as IDataObject;
					const _qs = mapCustomPropertiesToObject(customParamsArr, qs);

					const options: IHttpRequestOptions = {
						headers: {
							Accept: 'application/json',
						},
						qs: _qs,
						method: 'GET',
						url: `${credentials.gatewayUrl}/rsv/v1/hotels/${credentials.hotelId}/reservations`,
						json: true,
					};

					console.log(credentials);

					responseData = await this.helpers.httpRequestWithAuthentication.call(
						this,
						'operaCloudOAuth2Api',
						options,
					);
					returnData.push(responseData);
				}
			}


		}

		return [this.helpers.returnJsonArray(returnData)];
	}
}

That looks like it should work although you might want to use the newer declarative style which is our preferred way of making nodes.

I am out of ideas at the moment, Is it a public API you are working with?

@Jon Yes, here is a link:

https://docs.oracle.com/cd/F29336_01/doc.201/f27480/t_using_the_oracle_hospitality_APIs.htm#OHIPU-UsingTheOracleHospitalityAPIs-68D5DACE

@Jon strange is that the exact same request from credentials file is working with this.helpers.request.call in a node.ts operation

Hey @alexnemes,

That is odd but possibly unrelated as we have other credentials working without issue using the same thing (Metabase, Wekan, Venafi), Looking at the API docs I will need to free up some time at some point to see if I can get a test instance of the service you are using so I can take a proper look.

My gut feel is the request is somehow incorrect but I can’t see what it could be so the best option is to just build it and debug it that way.

Thanks for helping on this one @Jon :slight_smile: