#397 adds registerAuthCallback function using strategy pattern; adds tests
diff --git a/src/auth.js b/src/auth.js
new file mode 100644
index 0000000..cb8099a
--- /dev/null
+++ b/src/auth.js
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export let authCallback = null;
+
+/**
+ * Fetches the most up-to-date auth header string from the auth callback
+ * and updates the config object with the new value.
+ * @param {Object} config Configuration object to be updated.
+ * @param {Function} authCallback Callback used to fetch the newest header.
+ * @returns {void}
+ */
+export function updateAuthHeader(config) {
+  if (authCallback) {
+    try {
+        config.authHeader = authCallback();
+    } catch (e) {
+        // We should emit the error, but otherwise continue as this could be a temporary issue
+        // due to network connectivity or some logic inside the authCallback which is the user's
+        // responsibility.
+        console.error(`Error encountered while setting the auth header: ${e}`);
+    }
+  }
+}
+
+/**
+ * Registers the provided callback to be used when updating the auth header.
+ * @param {Function} callback Callback used to fetch the newest header. Should return a string.
+ * @returns {boolean} Whether the operation succeeded.
+ */
+export function registerAuthCallback(callback) {
+  try {
+    verifyCallback(callback);
+    authCallback = callback;
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+/**
+ * Verify that the provided callback is a function which returns a string
+ * @param {Function} callback Callback used to fetch the newest header. Should return a string.
+ * @throws {Error} If the callback is not a function or does not return a string.
+ * @returns {void}
+ */
+export function verifyCallback(callback) {
+  if (typeof callback !== "function") {
+    throw new Error("Userale auth callback must be a function");
+  }
+  const result = callback();
+  if (typeof result !== "string") {
+    throw new Error("Userale auth callback must return a string");
+  }
+}
+
+/**
+ * Resets the authCallback to null. Used for primarily for testing, but could be used
+ * to remove the callback in production.
+ * @returns {void}
+ */
+export function resetAuthCallback() {
+  authCallback = null;
+}
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index 6d09c71..f34c080 100644
--- a/src/main.js
+++ b/src/main.js
@@ -32,6 +32,7 @@
 
 export let started = false;
 export {defineCustomDetails as details} from './attachHandlers.js';
+export {registerAuthCallback as registerAuthCallback} from './auth.js';
 export {
     addCallbacks as addCallbacks,
     removeCallbacks as removeCallbacks,
diff --git a/src/sendLogs.js b/src/sendLogs.js
index c2da246..d420fa0 100644
--- a/src/sendLogs.js
+++ b/src/sendLogs.js
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
- * 
+ *
  *   http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,6 +15,8 @@
  * limitations under the License.
  */
 
+import { updateAuthHeader } from "./auth.js";
+
 let sendIntervalId = null;
 
 /**
@@ -39,7 +41,7 @@
  * @return {Number}        The newly created interval id.
  */
 export function sendOnInterval(logs, config) {
-  return setInterval(function() {
+  return setInterval(function () {
     if (!config.on) {
       return;
     }
@@ -57,8 +59,13 @@
  * @param  {Object} config Configuration object to be read from.
  */
 export function sendOnClose(logs, config) {
-  window.addEventListener('pagehide', function () {
+  window.addEventListener("pagehide", function () {
     if (config.on && logs.length > 0) {
+      // NOTE: sendBeacon does not support auth headers,
+      // so this will fail if auth is required.
+      // The alternative is to use fetch() with keepalive: true
+      // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon#description
+      // https://stackoverflow.com/a/73062712/9263449
       navigator.sendBeacon(config.url, JSON.stringify(logs));
       logs.splice(0); // clear log queue
     }
@@ -76,18 +83,18 @@
 // @todo expose config object to sendLogs replate url with config.url
 export function sendLogs(logs, config, retries) {
   const req = new XMLHttpRequest();
-
-  // @todo setRequestHeader for Auth
   const data = JSON.stringify(logs);
 
-  req.open('POST', config.url);
+  req.open("POST", config.url);
+
+  // Update headers
+  updateAuthHeader(config);
   if (config.authHeader) {
-    req.setRequestHeader('Authorization', config.authHeader)
+    req.setRequestHeader("Authorization", config.authHeader);
   }
+  req.setRequestHeader("Content-type", "application/json;charset=UTF-8");
 
-  req.setRequestHeader('Content-type', 'application/json;charset=UTF-8');
-
-  req.onreadystatechange = function() {
+  req.onreadystatechange = function () {
     if (req.readyState === 4 && req.status !== 200) {
       if (retries > 0) {
         sendLogs(logs, config, retries--);
diff --git a/test/auth_spec.js b/test/auth_spec.js
new file mode 100644
index 0000000..86029cb
--- /dev/null
+++ b/test/auth_spec.js
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {expect} from 'chai';
+import sinon from 'sinon';
+import {authCallback, registerAuthCallback, resetAuthCallback, updateAuthHeader, verifyCallback} from '../src/auth';
+
+describe('verifyCallback', () => {
+    it('should not throw error for valid callback', () => {
+        const validCallback = sinon.stub().returns('someString');
+        expect(() => verifyCallback(validCallback)).to.not.throw();
+    });
+
+    it('should throw error for non-function callback', () => {
+        const nonFunctionCallback = 'notAFunction';
+        expect(() => verifyCallback(nonFunctionCallback)).to.throw('Userale auth callback must be a function');
+    });
+
+    it('should throw error for non-string callback return', () => {
+        const invalidReturnCallback = sinon.stub().returns(123);
+        expect(() => verifyCallback(invalidReturnCallback)).to.throw('Userale auth callback must return a string');
+    });
+
+    it('should not throw error for valid callback with empty string return', () => {
+        const validCallback = sinon.stub().returns('');
+        expect(() => verifyCallback(validCallback)).to.not.throw();
+    });
+});
+
+describe('registerAuthCallback', () => {
+    afterEach(() => {
+      resetAuthCallback();
+    });
+  
+    it('should register a valid callback', () => {
+      const validCallback = sinon.stub().returns('someString');
+      expect(registerAuthCallback(validCallback)).to.be.true;
+      expect(authCallback).to.equal(validCallback);
+    });
+  
+    it('should not register a non-function callback', () => {
+      const nonFunctionCallback = 'notAFunction';
+      expect(registerAuthCallback(nonFunctionCallback)).to.be.false;
+      expect(authCallback).to.be.null;
+    });
+  
+    it('should not register a callback with invalid return type', () => {
+      const invalidReturnCallback = sinon.stub().returns(123);
+      expect(registerAuthCallback(invalidReturnCallback)).to.be.false;
+      expect(authCallback).to.be.null;
+    });
+  
+    it('should register a callback with empty string return', () => {
+      const validCallback = sinon.stub().returns('');
+      expect(registerAuthCallback(validCallback)).to.be.true;
+      expect(authCallback).to.equal(validCallback);
+    });
+});
+
+describe('updateAuthHeader', () => {
+    let config;
+  
+    beforeEach(() => {
+      // Initialize config object before each test
+      config = { authHeader: null };
+    });
+
+    afterEach(() => {
+      resetAuthCallback();
+    });
+  
+    it('should update auth header when authCallback is provided', () => {
+      const validCallback = sinon.stub().returns('someString');
+      registerAuthCallback(validCallback);
+      updateAuthHeader(config, authCallback);
+      expect(config.authHeader).to.equal('someString');
+    });
+  
+    it('should not update auth header when authCallback is not provided', () => {
+      updateAuthHeader(config, authCallback);
+      expect(config.authHeader).to.be.null;
+    });
+  
+    it('should not update auth header when authCallback returns non-string', () => {
+      const invalidReturnCallback = sinon.stub().returns(123);
+      registerAuthCallback(invalidReturnCallback);
+      updateAuthHeader(config, authCallback);
+      expect(config.authHeader).to.be.null;
+    });
+  
+    it('should update auth header with empty string return from authCallback', () => {
+      const validCallback = sinon.stub().returns('');
+      registerAuthCallback(validCallback);
+      updateAuthHeader(config, authCallback);
+      expect(config.authHeader).to.equal('');
+    });
+  
+    it('should handle errors thrown during authCallback execution', () => {
+      const errorThrowingCallback = sinon.stub().throws(new Error('Callback execution failed'));
+      registerAuthCallback(errorThrowingCallback);
+      updateAuthHeader(config, authCallback);
+      expect(config.authHeader).to.be.null;
+    });
+  
+    it('should not update auth header after unregistering authCallback', () => {
+      const validCallback = sinon.stub().returns('someString');
+      registerAuthCallback(validCallback);
+      updateAuthHeader(config, authCallback);
+      expect(config.authHeader).to.equal('someString');
+      
+      // Unregister authCallback
+      updateAuthHeader(config, null);
+      expect(config.authHeader).to.equal('someString');
+    });
+  });
\ No newline at end of file
diff --git a/test/sendLogs_spec.js b/test/sendLogs_spec.js
index 35686e6..6059724 100644
--- a/test/sendLogs_spec.js
+++ b/test/sendLogs_spec.js
@@ -17,7 +17,8 @@
 import {expect} from 'chai';
 import {JSDOM} from 'jsdom';
 import sinon from 'sinon';
-import {sendOnInterval, sendOnClose} from '../src/sendLogs';
+import {initSender, sendOnInterval, sendOnClose} from '../src/sendLogs';
+import {registerAuthCallback} from '../src/auth';
 import 'global-jsdom/register'
 
 describe('sendLogs', () => {
@@ -110,4 +111,42 @@
         global.window.dispatchEvent(new window.CustomEvent('pagehide'))
         sinon.assert.notCalled(sendBeaconSpy)
     });
+
+    it('sends logs with proper auth header when using registerCallback', (done) => {
+        let requests = []
+        const originalXMLHttpRequest = global.XMLHttpRequest;
+        const conf = { on: true, transmitInterval: 500, url: 'test', logCountThreshold: 1 };
+        const logs = [];
+        const clock = sinon.useFakeTimers();
+        const xhr = sinon.useFakeXMLHttpRequest();
+        global.XMLHttpRequest = xhr;
+        xhr.onCreate = (xhr) => {
+            requests.push(xhr);
+        };
+    
+        // Mock the authCallback function
+        const authCallback = sinon.stub().returns('fakeAuthToken');
+        
+        // Register the authCallback
+        registerAuthCallback(authCallback);
+    
+        // Initialize sender with logs and config
+        initSender(logs, conf);
+    
+        // Simulate log entry
+        logs.push({ foo: 'bar' });
+    
+        // Trigger interval to send logs
+        clock.tick(conf.transmitInterval);
+
+        // Verify that the request has the proper auth header
+        expect(requests.length).to.equal(1);
+        expect(requests[0].requestHeaders.Authorization).to.equal('fakeAuthToken');
+    
+        // Restore XMLHttpRequest and clock
+        xhr.restore();
+        clock.restore();
+        global.XMLHttpRequest = originalXMLHttpRequest;
+        done()
+      });
 });