{"_id":"@ilionx/oauth-client-core","_rev":"29-e2a44ffcea83e12ee01c71f441340eda","name":"@ilionx/oauth-client-core","dist-tags":{"beta":"1.0.3-beta.1","latest":"1.3.1","next":"1.4.0-next.6"},"versions":{"1.0.0-beta-1":{"name":"@ilionx/oauth-client-core","version":"1.0.0-beta-1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.0.0-beta-1","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"c82361c7941b401d851ac3b766e64eaf776b7a47","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.0.0-beta-1.tgz","fileCount":149,"integrity":"sha512-NpnyExh0KzSejB0czoGSs6OIHBSIEkH2wcuKA5L9b2uwjpB6hhTCMyzw4K9pVpzqBwYwsGxS7NTZFeoUzrPLhA==","signatures":[{"sig":"MEUCIDIYyAVWPiMREvnrxp0j2vShnbbdm2HakMb9WG+47usLAiEAloonnbr4EYjqgCDby/T/zUr68OfZ1mr7TX29+5zLdzA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":3006040,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhCVFhCRA9TVsSAnZWagAA7bQP/07URwd2ZkdQo/NIqDyb\nq0zVQ/QCiG9vdJ9UvykiMghXYL/akAiSMncH3C/sHVKE8RPs4neifnX7LmlX\nAiKXsnrCpM+jQlV7j75ZfOQravazCxcBI2exqjxzuNUFP0HBZ7IH+18ZA/wZ\nRiGQsvaf8Hc5NOAFEFDNfSUjof+vl4zX1prhyyEXjI2Nd+65j7oNDaT4zChc\neJzKkw+oE/51KeUx2gFMADjTjzdKX60LiMXIwPLXf2iFDHJr7GVRBu88Z++q\nbVPQvP+k7bmurxy0ThPUHq5ddY6Xay92JTqpg4uBmjsSgBK5qbZajgvEp+Ra\n8gi9b1tDnLRm0qiw1P2FSI9yg74xja/2nNY+OxaH8WJ91cPVThz5t0hZY4k5\nASVFehSSnrGiACy0XsGzQAHzicYbXtvOY6/FzjRmfzNnJ5WKlz6ZcJ4lpoKj\nZZnfS3ifkVRPQy3PdTZhHgAfc5p+uIsWWwnfIhLEraXkow1+Qgrh5Nb4ECLF\nCfrrGAzQ8T2gwcpCixYQnUg/E6Za7MEj5qBlyAkOPPVMJ9ISHYUngV1wqVYb\nGiUF6PY+PWionav4Qz8hJD1k6P9dFHZ0/eArlu+QHYUak8GTjPFtnpyJMsXf\nqE/N9MeHZAB9+oDzx/pnPwTIM07i4fAdK8ubjEJWCCNPc9CeAMctHhhon+qP\nLp9/\r\n=FaJL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","source":"./src/index.ts","exports":"./dist/index.modern.js","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"description":"OAuth client for implicit and code flow with PKCE","directories":{},"dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.0.0-beta-1_1628000609288_0.900510945935604","host":"s3://npm-registry-packages"}},"1.0.0-beta-2":{"name":"@ilionx/oauth-client-core","version":"1.0.0-beta-2","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.0.0-beta-2","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"ba0ff63fb33942a1db3e4e037bf753c744b13bed","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.0.0-beta-2.tgz","fileCount":138,"integrity":"sha512-HInRTlw247tpChYkS1qLZC6m4ptbA2234gxnnPyFjpaQNhBniRa6uIaD2gWd10bymRDerUPHmhApP0+zxgXl7Q==","signatures":[{"sig":"MEUCIQCrL1Q1zbflD1M6uNE6s9RgYzXQpwg4OoyHQbZIzPvFUAIgSySOezrhtRNfScSF/kh3qpsPS6i6TiZuLweP7Q3NW8A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":3001536,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhk8bcCRA9TVsSAnZWagAARSwQAJ5a9FOZ24ad0NMmbFif\nGLwanZN4oVpDe6TBX0ECBZHC1Ua2Q4t9yrsHEskW18UJkJ2OyO1XRiTjXxvq\n0p50QjbZaJIuiyOBcrh2+FZaOxopVMvDdI+0Enev9ypV09iK+ZJ1LdcHkOOX\nk5Y3DNMGtFPFY3OFS4JrW/zLm+LQQvq2bSgmH0VnGF5YvedCsZCr/vfnEmhX\nSAB2hxzZ4Zx2VvkY2N9gSmkdK/odpFXy7TE5k91JB4NyfJeyf/ye1xRspdw0\nZuO3nGesFRVVJUODh82A6mZWS1WGsNeM7oEwblbjcGa7rLWQtjKXtRRpBLM9\n2Daul4H0GQtbJxU+L7Rb5jmKXwD/+T6q0WwOVsE0HRpVwjE3ic2t6VsC3P9C\nGYpIpa7kO/eX1WIO06PWOpAxNQ9HYRWESoDQ5vb4DYOrf/sl7bRka45o+8D5\n2QeUditf1Jya+Sc3wVxPuMvpj590gDGfpCKR6K3weic5FPQeS0hk4EBesub7\nors5ijygaLpngNJSixH+YAknMigT2QGcV9QJvooibEeBaW42GtsrhiUONnvq\n0ip09CkwNl/i3lbrS+NydRirl0FjkdXJUWlNMH5sq/LRuxjbLt14y3q2eE70\nV7unuI9r5CNxws1+7brHD2ilg+0OX6Oo/fPI3krz/jv8pYkDA+kjVdVTIZvH\nOhPg\r\n=t4vH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"[![npm](https://img.shields.io/npm/v/@hawaii-framework/oidc-implicit-core.svg?style=flat-square)](https://www.npmjs.com/package/@hawaii-framework/oidc-implicit-core)\n\n# OIDC Implicit Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://github.com/Q24/hawaii-packages/tree/master/packages/ngx-oidc-implicit).\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party Implicit](https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Add support for [Code Flow](https://openid.net/specs/openid-connect-basic-1_0.html#CodeFlow).\n- Add support for [Code Flow with PKCE](https://developers.onelogin.com/openid-connect/guides/auth-flow-pkce).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n\n## API Reference\n\nThe API reference can be found in the `docs folder`.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@hawaii-framework/oidc-implicit-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@hawaii-framework/oidc-implicit-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@hawaii-framework/oidc-implicit-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@hawaii-framework/oidc-implicit-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@hawaii-framework/oidc-implicit-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@hawaii-framework/oidc-implicit-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@hawaii-framework/oidc-implicit-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@hawaii-framework/oidc-implicit-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@hawaii-framework/oidc-implicit-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@hawaii-framework/oidc-implicit-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@hawaii-framework/oidc-implicit-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@hawaii-framework/oidc-implicit-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"raymond.sanders","email":"rsanders@ilionx.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"description":"OAuth client for implicit and code flow with PKCE","directories":{},"dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.0.0-beta-2_1637074652423_0.9465562635961131","host":"s3://npm-registry-packages"}},"1.0.0-beta-2.1":{"name":"@ilionx/oauth-client-core","version":"1.0.0-beta-2.1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.0.0-beta-2.1","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"a1119c98d3ba5290b2b576b66cca5da7ccd9607d","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.0.0-beta-2.1.tgz","fileCount":138,"integrity":"sha512-IlftzwD3R0ud+DaR5nAdLBuMUL2DLG0PQUcFRPFPunuaDKOsdRVhV4T2VIf+7qs15D6260BJg/0UBD3ENF0FjQ==","signatures":[{"sig":"MEUCIQD6/52qeUXV5nv4fBObMnhYgt3i2EBGsWBX/GyhqyqoRAIgGNIFbftf0TkCquI7fr1NyuonG9hmeYCpesBGAs84PVI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":3001658,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhl6wZCRA9TVsSAnZWagAAmeIQAKJiFtQbubFuRidDmPcu\nNwQdox9gUcnJVwovellst0ztcWBLWjOUlfIb7mTHlV0IpWoJFf2uBjreWHpZ\nkIY9k+Bon1qzM8yAQ6advBJOnxpuM2QudPhJnO8X4mD79+3hRyH3VTC4BKw7\n0bwJXDgPVWvWytEivgPQ4u1300d5BJgURsKczBMD3cWM8VFj69MVQpJA1Tfe\n2MEOYzVfwqj1gIzBwBCHmxhzhNoSlhxoyphVAOznQ1VCmBHUxQpRwIM+9ndO\nN9mHLOy8d7cj4te82Xd2uEF0+qiZyBAy4DtKqrugKZmi/Sg6Pl6GDFu2RIJO\nRUkVoY+zNWErNcCnBwIsDVjjkOGSiJEtg23/E6xt3yKzgF48sF8FDvpGPrOf\nKvxEzbyFa0UzFG8dZw3TiIZN0Svg2F7nPTVjvFSkCu8JyTtJfxoWaMZEw4w3\nm0S7R5b0hA/OYtrjCmtjOjRqKuc+51gN6YXYBKg5CQ4B60Z164r3kq9k5lK5\neh52biwvuZwzYmHQMmV0bRI1Z9Qyt80n5LSNIh6OuEQxN9fc6pexvVdiQcVx\nWmPafl0O63PxkESlKfORwSITKdwb57gDh1nnQRAVZT1Z+uXaiLCqjFT9/7Hi\n9wTrSAM78z51ztAFOn9TfAxbNippFceJe4dTN/ISHdoXlE/KTHxj2TlchaLz\n8AUZ\r\n=gs1C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"[![npm](https://img.shields.io/npm/v/@hawaii-framework/oidc-implicit-core.svg?style=flat-square)](https://www.npmjs.com/package/@hawaii-framework/oidc-implicit-core)\n\n# OIDC Implicit Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://github.com/Q24/hawaii-packages/tree/master/packages/ngx-oidc-implicit).\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party Implicit](https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Add support for [Code Flow](https://openid.net/specs/openid-connect-basic-1_0.html#CodeFlow).\n- Add support for [Code Flow with PKCE](https://developers.onelogin.com/openid-connect/guides/auth-flow-pkce).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n\n## API Reference\n\nThe API reference can be found in the `docs folder`.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@hawaii-framework/oidc-implicit-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@hawaii-framework/oidc-implicit-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@hawaii-framework/oidc-implicit-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@hawaii-framework/oidc-implicit-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@hawaii-framework/oidc-implicit-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@hawaii-framework/oidc-implicit-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@hawaii-framework/oidc-implicit-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@hawaii-framework/oidc-implicit-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@hawaii-framework/oidc-implicit-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@hawaii-framework/oidc-implicit-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@hawaii-framework/oidc-implicit-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@hawaii-framework/oidc-implicit-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"raymond.sanders","email":"rsanders@ilionx.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"description":"OAuth client for implicit and code flow with PKCE","directories":{},"dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.0.0-beta-2.1_1637329945215_0.5522041281231573","host":"s3://npm-registry-packages"}},"1.0.0":{"name":"@ilionx/oauth-client-core","version":"1.0.0","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.0.0","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"d02634c0c4516229f2c349d4db8d7a05aaa22571","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.0.0.tgz","fileCount":110,"integrity":"sha512-IyN9ukmwJoyQ6mTnFefRIvmWUng38x4xqQqwURgMv70DfNza6pVM2URBBonpl4wQjGYaz9Ho6kTuCl3n5bLwhg==","signatures":[{"sig":"MEQCIFKQCR/nuo3FdAIoNS9zQp3sti9BizCNGvyVR7/gnHyaAiAXnXMcVQE9cLjDj9r3ueujyiiwK74Ho6LdrHePqQI0vQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2538192,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJijLELACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmrx7Q/+M8bd8UuE5l3AWyKI7elNctE2QeXQ8s1sQftQIYDSgKc7PJPG\r\nPH3qYSiKqVl1XUOruW3YLIROcTS5PW41CninmyYFogXoiGA8wVCebDwff86b\r\nYTgMS5W9FAdoAEJBe9V8Ak5MM8ZkVWD9E2Qm1puZCVDuSsiDFogFjBIP5qWS\r\no3YrNBc33AmDV1L0JNappWADtPIRI85jcIV2crpYnWsqlE2a/diGkkhM+w27\r\n6lsy8sazFM+Y5G5qR56nW+qea88LgFkMwavyPj9zH4IgMfXQMlu9b9TAP3uc\r\nepgGiyx1h1ZUq6epy/fLU77HhBrtqgLxLYC8NuFypxLywHAwizzdSakZc/h6\r\nitbStVnywRpOF8R8Vjegh3eqa+5rTgwRCoUcrRXIChDp7XEiBYcFCjAraoBH\r\nMAnF6mnt6Xbc6ME+J+pH3djIlcQJaBwIIExYMv+wcR5JBMrT+huhrxJo7r6p\r\nonUz7MjwaYp04obHqULPav8Ob6ixqhSDFfvahLk/8aOe6i9Ce+UyddK8ZXi4\r\nStGIhMf661f7cCqntoY6Jpu91T/iOZ9hOSftpegDhNqX9310TaSu02VEURI2\r\nkcx0lf5SIn2UXEk2tIYYweANj5Fyg+JOF9pqwxMrBEdrSUsc4eOKb3paFw1Z\r\n3KgR4Cs8F3cvrWLMJz/5nbf7iwIf/GVPP2A=\r\n=ZEPV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"7aa96feca0c09f7829aad496d1460c3927325648","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"_npmVersion":"8.6.0","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"14.17.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.0.0_1653387531309_0.007184359291040332","host":"s3://npm-registry-packages"}},"1.0.1":{"name":"@ilionx/oauth-client-core","version":"1.0.1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.0.1","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"33d9e3b59f611386fbfcf5920080856d94a5e2d3","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.0.1.tgz","fileCount":111,"integrity":"sha512-Qc/D8vGZDsrSxCyXZO6C2Kq3fZvJ3brWKkUnp8FJYHHiB7EUdUChOUYpKwtz16yrZWXgYpHQNQeEvfdQuSAcOQ==","signatures":[{"sig":"MEUCIQCK0BCFtIAhTzt0igtCFbCmMReIHXH5wY+jLiWrp2MRRwIgQflBy1HfALIc2IoiI8dMf2Z+KqNxQ3yfmEQcBrPUbCM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2536689,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJimcW+ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq2wg/5AR+DMEFY7d6PvvDqbaaEHQ4NW6IS79DVz9xhwG0ZG/B9nrxD\r\nRXh6OeY9j2htga+cTBBaPUwI1emkN8VghPpiXhnt22kmXKp2cpkehBlPyv7I\r\np7cA3/teH2/4whmQxxjJPXW2VsVcJn6QZEPjgpLnvR2EuHVSwP5j7wgoRWw6\r\nP4So3V6LSn8JzGg6c+3+lNoLWBA3ZceKO1M+EhhitHPhWP4Z+1ZPwU9HUvQu\r\n3OJ7NdwQX6gUL4X6NGqGzDPMoAneUmHm56k331fEhteUpegoSZ7KmoyVwR3v\r\nSc1a7a1nwteaL89/mJAlUicy4e8eiH32x7p/EuogX/i3Iu2gDEUAh9NBZXCv\r\nxi8M4/dqYy0rrjcbcD2HPeaJPWzInDNuZmmMZXKprUPLRA5Ek1QK+tqG56p4\r\nrgIkljlI1PutyzePeDq+wrv+UDf/ZcwNqhHhSgwCJZoCV7L1SAo00KKyIZ9t\r\nCTfvW/o0K/YWoxSuZqkFoaUTe5J96/zalF1nSE//NAMxvYd8U0u6bjMslxji\r\n2xAXeanuMW3kzhezVeCUWnf1ooEfnh81Eppxj+xVZHSaOD2zeecbAOzRMk4b\r\nX4FvYwRYdbDa25i/61hkj9fiusvqMze5JDiapbIOm8NyxIDMKzT/ylYcQ1qj\r\niZfU0VERyx6NZmIW8x2vT3UgkJRmsRIj9kk=\r\n=OBKR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"0e7299bb19c3dd2c4c16e086f3e58130f6b4a270","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"_npmVersion":"8.12.1","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"14.17.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.0.1_1654244798478_0.1259941596246863","host":"s3://npm-registry-packages"}},"1.0.2":{"name":"@ilionx/oauth-client-core","version":"1.0.2","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.0.2","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"31ae5d0240a2d47a3e217d82fef70b8cda73bbed","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.0.2.tgz","fileCount":111,"integrity":"sha512-ZVGknagOJC7szF4ZSVlpfMr6HDv3xRtiXJib2e+V6YK/KPmW867wo3j6eS6avj29q/Cbk/QM/gYIIoQ+hDSy4Q==","signatures":[{"sig":"MEUCIQD4vqIV7IjeeW1dP/80Glq7iOb8qWxU2Hkb3+STqynftAIga933xTqNSPvnOE/D+1h08AbeneBLV0enpE+oPzTfV7c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2536689,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJimfkDACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmoyng/7BmWYs2R96FQtFie9tOd9UnEseIEK0VdCAH2X032Qw5fgsr1h\r\nBUL9LXEMRVX+whsuQJEyC/ujFJ5ip9XGr6LaSfRag3Q2nWp6xNPpPvt+4E9Y\r\nnzzdRk/mA93H3P0V957mEMp03TExmCSGaAW7j7+XeIveV2l2qIsgCLuRTaoE\r\nqMs/q6uiGJY38Th/blwUHnURCk6FGZnkCurvR95Vk5tJHH70yjTVQgoHPY0h\r\n0d611yTrQqjgI4J9M0n84ILAlpCSuX382Y6wCJWvShVyNU+oSCwZXffoAHMV\r\nSchx19thaAOMfVRJzYJvo4TXU5DAuCVkwF174hJ83b1IPJn7j4E34EcCmQN9\r\nvM9MDmKhCyoEtBNM1DmGcVQnu3eaAimWhXMzhZictWBXus8YFO/QlmsRqhgW\r\njTAzJW+40oiAqrR7/bOzW+4f9eAdOm6za8XP9pZQKBMrfGneehn+J68UixAP\r\nDb/Tw/YoP+5qMJiwSsT7WU9qE2ZIf9E4h6/YJMorXwEqAM4cOMT0D9XZKQSI\r\n8xuDa1yw6j9oBffzS7U9P+ptTrx1hqYvtgXpGrrLaQGHbpEy+etwNETrgROx\r\nmrPPAUdbrSS/oKphpY4nZWqZ2S9YQPu51T3/CN/8jKbzjuhrp3yfD3uDA3Ce\r\nwj66s7kkOc6/08sQM52VIvOw3zHRWUfLe64=\r\n=06Dd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"dcd3fbcc3e141bad279fcde64a24c832305a5a62","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"_npmVersion":"8.12.1","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"14.17.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.0.2_1654257923436_0.9297889042902296","host":"s3://npm-registry-packages"}},"1.0.3":{"name":"@ilionx/oauth-client-core","version":"1.0.3","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.0.3","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"bfad54446beee0d5fdbad0b8121b9014666dfa00","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.0.3.tgz","fileCount":111,"integrity":"sha512-Ubx0iD/lM3xtWC6XQmTYxnIHH0rfsRWHdFQGzx7IgpGBzumfa0YI1LMcNdvM8eRCgbdILkQzY9ucKCxafrJ+Dg==","signatures":[{"sig":"MEQCIBLvkJyX/D6NLs+VtSvArrdETN39bmjFEpCi3stu2N89AiBsZrPaB2CUksy6Qbx5aqfB+UEzv3bfKujACKFJaebn2w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2536578,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJi2rTdACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoubQ/8C4sEVk978y+tbVvHFEBBUUTLGW86+wKlR7h/vhDLm3UA3Fdp\r\n9q7Ty0vK9OIFsy4MkpY8SMbh3MAvQbgKd59KsgobXW3pnKQPC/M0gj3oQV+6\r\nU9EDbX1lh9eYVVFD+XLW0oBZSScLOv6PghGKK+Rjc0zm0ZRcritRoOMPLqx/\r\ntGUYBwgZA58R7UQdWxee8Bxvz7AHp4r3L0OtpBt4BtBR+Xpog4d1YycL0345\r\nK+DNGIBOIaVPQY9IeHua19CBGCuWeVThL7HvT0Wj3c8pd5WtXDDhC3xMFTRq\r\nYWOn6rdYYvaniZm3PmE7X7yLNlCTFT14ay/H1aX2mm9QZS/jlRgllDOgJeh3\r\nFx3OuxMQ0hQ31iEhLWzntEq7axLOPMGCzIMHfFlq5ZWuadwWIJ6bs3voxm0Q\r\nYK/sU3k+KZklpO2DBXJsSWMpavkQSEYUi3etEORxinfHUfQsgExxL9En/Mf+\r\n3RwGI+T7NebtctWJMNVpTn/9+CmdYRbq4Jnj9OCxvHdxIbrFYj/7XqVs5coF\r\nRjdp31cbLvOGhwzopzIKS0HPkyBZIgoYFguK+JewWp9iwJEq+UiyH51iewwF\r\ncFzKdXsFm0Ybqe69RZ0kha6FMSfuz+mSrMyQ7o1ZYSaTOIrsXPa0D69/YgGb\r\nBIVTTiz+Lbb+8IBvZXlUZ080M52I1KCjQeE=\r\n=Esci\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"92ad1ef4a585c8de27bfa9bf86d405ba6fe42433","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"_npmVersion":"8.13.1","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.15.1","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.0.3_1658500317640_0.14340116756690224","host":"s3://npm-registry-packages"}},"1.0.3-beta.1":{"name":"@ilionx/oauth-client-core","version":"1.0.3-beta.1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.0.3-beta.1","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"7260766abc9ea1db1131b58dd620d4e0f41740af","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.0.3-beta.1.tgz","fileCount":111,"integrity":"sha512-OoEd+nBZyKHfaQEAGFgaGTvbpsUDf09lruIcFSspmQVNkIXJCnp9tgpr4nlhMGlyAwEj0N4V1USLAf+ofqmYAw==","signatures":[{"sig":"MEUCICvUJaQ3YLMWkfFUN1TLKfTWtjytzNz1p6AfXfEyaPozAiEAwCSkEI33EjMH36uVUIRRz5yj32zS+hq76veHVIG8o68=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2536952,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjLFi0ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq46Q//RRBCsY/+ojsem6A4hKlJx/fi/rJ5aw+7UHx/sVHsXKrUpaNm\r\nfr+wwSSvTtJPkkNd5FF/o9SIlOXSGd+C7z0uOl9yrOGE2XKBQ8EZwgJJmedz\r\nnX0Qk3BPJ8uMzo4v8aR97EL+t8MA9U6bM7ppPm5F/OackZz3D+tPSwGO702V\r\nKtv1RazarHxU7HocHre//v0nVBGOH/gdW4D2LbWROt7oh36vYF4IO/pQzwXm\r\nSNrLQsWn6jXLlGwAx22s3ox9WUfnoZi2jTcCmaChkDgc8i1WQZp/mQdzdYUd\r\nFOdFYoy76kt0aYm0tPiJgnEuU1UcbqGHgIK+pKIpnEdT+xfWqjhFOkqd7iIF\r\nNm6ny/hiT00c2hP/X9UgSl3Pb+MaT+fvJPi+ZiQv9dduiZmz+fFKVn7u/1/n\r\nRhiP544zWzcUlNMn5xnQa5QuGa39nReUgT5CYpDt1Sr1Tr9meuBdZYK2QsYh\r\n/zsk/UYEkI02pUZAGq85Kn1SPCksdsZY+Hk70DTFyGrVPPNa4RLhNrXaxE8/\r\n3fs8GZs6I3PJFotpEb8gGKJmU/oz79M0oKWNBmJAtwdAi0H0oiuElZzmRD+4\r\nyu4G+h0WYUy+t27yUyCJvt8EfL0V9jhsx0a9+RK5Z3OJby1UvpA7Wvg5Rr4Y\r\n7jsnedH7i5H9VYAxtJN7mHhv1Zr4KuFzGlo=\r\n=gxyF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"5af81345bdfb74341448a4277c7032a8644e6cf1","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"raymond.sanders","email":"rsanders@ilionx.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"_npmVersion":"8.15.0","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"14.15.5","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.0.3-beta.1_1663850676058_0.09003487245202102","host":"s3://npm-registry-packages"}},"1.0.4":{"name":"@ilionx/oauth-client-core","version":"1.0.4","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.0.4","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"8dd24958531e83e81adba8e1e01a0996f7788c7e","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.0.4.tgz","fileCount":111,"integrity":"sha512-lqYxsArSRlGqh0Q8ujvJgzlqSI7x0Cb4JRhdW/3GquAq8xbkdNPR92aaRbzmj5fMhFX6Mv2Fbv0CalclbBx2Xg==","signatures":[{"sig":"MEQCIHzLj2e9hGxZ83dYc9Ze2Zihudvtca2Xz0uvhV2ZAtpvAiAse/9+7vnQpkDRsHieY+qHskC55z6+0At3wO5l4xINfw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2536945,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjLF2sACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrS6RAAi2twWq1+GWCn8Uw0nPtuWv1R+vJMHONg67NLER7mJqe/yQBJ\r\nlAJQlyPCmGFgYc7IyGamnK/gwHiLaIRgToRsTbLTj0DdMSBNGCtQ6/zETrV7\r\nlC4McxZQZT0u/J5kNn3kJYZHL+uhCBSjgZgRQBhVOZaWVJ6gKscv7VhqQmGE\r\nxtAjhq7Hi6eKwqGwuJAIlA0zEwUDI1iM590dR0RfBCU+NUCyFeLkCr2WsiuE\r\ng13/cSejqWP0xU4kx/py3+9SIl3v8BBugkMofpX19pyarG3vH12Lk+JZvwQi\r\nooPOYjrH+weZ2BA5HOqDrNeJau+DqT13WYnJSqPyC/e7rq8gJTZUPKEdt3go\r\nMVKJA91ZjBBA61YzMcsKg/B0gspbEIk15RchXv1e+UaRvvLlScuujEy6zepy\r\nvXdkZBGJkRvpXnCUPNH3jMXqs7Ig9QneCBAgN57+ooE700g+Z5xawYCDRTaI\r\nXv4ycLRPRkUBkAJq47io1gOq2AaXUnbABdvrTxs1nmT16PfFQ7xuR/nFau3B\r\nnP1xzqyyBS7Ui/++XZmUfxbJEkaC3EleeGtdgvDqshb311qqETxSY2QbsWb+\r\nnLpMsQehFz1Yfmv5K98A0hI+MdPYBwoFat8S+uYokoQWmxMJCaTc89gWoxYT\r\nWOea4kl+fujcZt1AP3nIjkKtlPBlrKlXGYA=\r\n=KPNt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"4bb1374e9ad0ef9671613bb6beeca2f9b952834d","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"raymond.sanders","email":"rsanders@ilionx.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"_npmVersion":"8.15.0","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"14.15.5","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.0.4_1663851948070_0.14840436810886248","host":"s3://npm-registry-packages"}},"1.2.0":{"name":"@ilionx/oauth-client-core","version":"1.2.0","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.2.0","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"c7fbafe7334538f14efd562c2f6f459146fbafbe","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.2.0.tgz","fileCount":111,"integrity":"sha512-auIESW8pCslKQVK6XT05r7aB+C3wccr1WSIRgoaakEPgexVO42D8BBuwkbf3k0iIwPSR5gr12yex0mH48PFfPQ==","signatures":[{"sig":"MEQCIHS7rcswcHhNXDbpWmUZUZMxzA9qUl/AIxGXNozPx277AiBNSBWoRCN18b/9RxGbVF3fFXnUnWqDZavtjZH1UsaOGg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2536945,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjNU+6ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmr/fg/8DwkyDbrIEvmIvrDycqaExlNqiYFn16M34LTn2ybi+KlbycQW\r\n4IuafQkpgmQWGwCjnoaK0ro6oNo4G5+c7EXkni8C63dnwNnwhkphon1DnaIH\r\n9sF0R4uFnxX+hrzJnGEIqGDi+8JT4eWYIpSW9TXJcb2KIx/ZjB6pn5XGSVp9\r\npqmDBPRNnMSDRVGv1nyPLIVVcv5EWFsX8bnxOornx4kCN8vbDs30plzzfqSV\r\nOaYVMaXmFVB4W8rMHaUuSBVtgkTg3ihFSb8c2FWpRBjnMyq0j62DHmMYrjvA\r\nflzOtQGg8ZoPAdMTmhl4AwMz76UL6eSczJX+Gk9kosXMKhDSlTIL64fMJPJd\r\nxShTYQQgQPIA73CGScK7miSyYsQ/WRnMzCqjjlBU6vZ3hJg5X7M+VNIi2naw\r\ngIe2GZv7wFw8xIa2k6OuV739CrPI8kFKvK/mzsBe2kE8PILpbv2q+m66Pre6\r\nac9DGFlX/tbcTASslFhf3UQyLoianR1GCPQFlZwdUTxnBXnlCuKFTrtE49vo\r\nNy/xxuD/GT9wafjCmAKsiuqYSqAYdfALZKhZPjxuE9PjjpM0l9YPBvHWZZ5g\r\noxjvOCYslR+Gu1fNB2l7ilQi8SxUEgPrzo6bOmkUJigA5u6Jl7E5RtAQbqkI\r\n414LXZpgmOwTML4nwD3JrsRZRmzs31khbdc=\r\n=CGrA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"cfc1a6ff8c98a78441b2b2ea9948ceada5e9255b","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"_npmVersion":"8.15.1","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.15.1","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.2.0_1664438202206_0.7603177065806426","host":"s3://npm-registry-packages"}},"1.2.1":{"name":"@ilionx/oauth-client-core","version":"1.2.1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.2.1","maintainers":[{"name":"boosten","email":"kevinboosten@gmail.com"},{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client","bugs":{"url":"https://github.com/Q24/oauth-client/issues"},"dist":{"shasum":"490126d32238c902eed1ed3a7e942b822f8f2bd3","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.2.1.tgz","fileCount":111,"integrity":"sha512-yk9Q3ZVISLMlXK0LRrW+qriojZsoJdTZQnIIb1YUhVFxz6WK2tDIVb9N4vvNHH0vQZB1q0gUbLxEvL0VI0TFMA==","signatures":[{"sig":"MEQCIBtyCHzfY2zbCBZ9nYaEqCc+mAnR6e5JldVGe8lyK5MoAiAdPDOy3zchRfXmobKDEo4i7OEQyJq1r7D9or/wfXrEaA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2536945,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjOqL9ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmruYw//c52pg5AlPJifHYV1S2Aya0XxoB9qTRDf+egCbw6SXJVWKvwL\r\nqPxgYyJ1W7Kb9yiv4TcbnLt3SmQN3neS6+leXv0nRZtz3gXGV7uuOKUiYkFY\r\niSpAzytqAuMKysX13h+WNQ44cc3eVhuiLdipkByBtivd38NgE1G8ghTVYYqQ\r\n8exEq43Ug5lZoI1CnVdxsrmganrQNugezeBR8uScfJO33TKgeCCBX14uPwfe\r\nUxJLj2sFjbCwNPejpqRByY7HWbXvXqbKFmuXdReCT9/ONe0rEVIaJgzNqnRS\r\nBGturUDJA2pNnySP2meKjjz0LAn39PMbhj7Y2PAb576rqwvywLjedqkXUf/r\r\n9rDk0PFd3FPXypQQsXb2ffvFuC20k8vATEJlxj76my7KvUI/eEk0zvmZrBRN\r\n4OWoM1smjajOAMpwpdqXGKTWnWl2i4d/SbJGrj2ffKnDD/i2U/R0Ie0/QkrR\r\nHwldwKpRBHIOr43clLg2pC7Jj6RwGld5bX67262dXct0Hs+PiGe8SoPqwGY5\r\nP0TbFlXlC56AQzq4UXYlIpuSFsEtap1JkvFRxQK/8Sv42uUwLRr4ClvUeegU\r\nLGw7d5bC3p1MoiOCBJQJzyftn59jLDtZ88CWASmOujWqJ57l6S+cS1l51QD+\r\nJp8IJ7avMHEc1qmkPS1HKLf/20rNe7G5FY4=\r\n=V4iR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"ba3a05b1f195c123c44c403c63b404fe25069df6","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client.git","type":"git"},"_npmVersion":"8.15.1","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.15.1","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","jsrsasign-reduced":"^8.0.15","typedoc-plugin-markdown":"^3.10.2","@typescript-eslint/parser":"^4.28.1","@typescript-eslint/eslint-plugin":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.2.1_1664787196887_0.7998458532570494","host":"s3://npm-registry-packages"}},"1.0.0-next.1":{"name":"@ilionx/oauth-client-core","version":"1.0.0-next.1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.0.0-next.1","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"8a40fd3ac9e44cd25649ac3ccd587343b63c9675","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.0.0-next.1.tgz","fileCount":113,"integrity":"sha512-RlKhsTuBXneFFWCAPb0Ve7YzRRJkEUkphYFZiUDIa2rsKiRqivxMyKOeJqXHI7B9VdLU81mX0Hawnofeh+wBFA==","signatures":[{"sig":"MEUCIHJdMpllB51Hx7pvU5Z53B75SHB6uQsXN6ydxURWdteBAiEAgy63+oCEXqDheSyjpt0ojUlVMM/r37X2WFBrT5df8BU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2556361,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjSBHpACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrR7w/+PUIb0VypNnQjGza8mchL7vTuwZXb1whVUEE91OLj1Q3B59FT\r\nTXff+YjykFwsGHbS7mBnSK+0KpIubL7ZqfDxFm6LyMI6Te+xlfu84lXweMV2\r\nm4HrF6Z6zxuPPEd4qx3NnHsADI8GM9YwICjofqPdHxeGwHJOrxLMnU1njP0w\r\nxPpB6IRUVnDkdfdDAoOdrbiyc31X9xINUiRsQqaxpe2i0YTmJRGmUf7vNN7b\r\n3ivVhNSE5aP/B5i+l5ogc1JUOtGdoJqJcLfR5Qdl9zE7eG00mPaKwHYC1mU7\r\nxDD/3y9cdIJQ7JZh7v/mHQo9zF7tMWwg/E9Eg49G+230sQyoHuxBFOVYrqzH\r\nLJMRt7p+FYfZZYr9facMnuDOgB0WYTnAyG0DufyOk116Y2n4SzmMrzKQb2Gy\r\n8upVxbU3TFDokGelskhIm/QnRUYl4Yr5DXinsxxkoKyi9/laDsvclOdLjnki\r\nhCYG6C4uOFyTXLGJoXOI4KsI7TdYcGRLoyHrUmLO6Qj9bBjvYEGcCtXQCCbk\r\noKYR/0pdPUaJWqMiPB2NRRhXv3acWTg1NmhQdlM3SP5s6vDI7wax6OeyUO16\r\nHUs69q1L5AZ0xbmKIR1JJBx6kGYRipj7Dl1OnlFCWaXOdBFUFSIBB4kMqxbz\r\ngteIp0//U7L9XHX+jsRkKDbk9IvTld76l5E=\r\n=k+zN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"9b66784b0c9f80ec976b09eea474f34fa3628b8f","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.0.0-next.1_1665667561643_0.7747365781719868","host":"s3://npm-registry-packages"}},"1.2.2-next.1":{"name":"@ilionx/oauth-client-core","version":"1.2.2-next.1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.2.2-next.1","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"8e0a863a73a550297e73b0fe635d1cc3baf3fd0d","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.2.2-next.1.tgz","fileCount":113,"integrity":"sha512-kLYWDKJvnubGxiprgFI0MnbCFHgTHhLY0rIe4x/Fx73mSSJ/jAyAn8l4rGwa6zYfmytpdIHGao7q2+tEF01IsQ==","signatures":[{"sig":"MEQCIFb+CtZfZhHP34VbMLHS58PXbeAN1eGsADtrVwOiNqHrAiAbpJYfPW9t5pMJnfWW1j6iSlqwovUH1epwNgMbV2458g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2556604,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjSBWKACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmpu1A/+Mwte6/dABG2S3PkIzhRlyk1/z9u7sUhfNNylTILLigAKRa4o\r\n3wc31MKenilQDOh7u27JupMS0J3BsFMQGHxr3nFP/qIcoEsGbxQOIhQv/Hsq\r\nsL/6/qqJgjaPmdGosbN2DdO2p9LCQMPXQUWukADiwcW3wPKMTamrJ8038ulb\r\nKlpU6AIqhgM4nn+SnzwTC1g79SBT6niQblvRRp0aC/brGv/cSFUVPldi6E4Q\r\nAHXfAAPjkjEHwLwrDhRVNN8l6j580/8umgPFBR1MvrfR+AtIqrOpgesculSd\r\nk04Wk6fKYQLNjj+fd1MJ0oWrMtvE2Eh9k5q8H0zRDWCvHXEn50JGDg+bgKl0\r\n1xrodl0b52frUBTm4rbFxBBcBUVfFxRA197iU9aWgHfyZRlur1j76zwuM9p+\r\ne7as1bLZNk7StYyH2W51e8IFce0m6cAEGfPqg2GxxQRquexrRHIYX8x/O6P3\r\n2NjDk28phN5geAUOnXJQic5/Epm1APl7dqCGVl6hF3QUsoPbfkLfXuUC5nPc\r\niTjxTsBFMnAUrti+T4/wfKbKocn0XWaRqjdIfTNpl3ac5v0wbgUuHwbWS5gg\r\ncbVSU1QcmIpFY/a2KuOWDE3c5WYr3PCdRQM0cIUMZWiJ9MFVKvV81xXIiX7h\r\nk5tsrNDjJQrzJIhw9ZRE9LY01nkMb7E2mnk=\r\n=kdBH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"cedbf76a24d597c97d2e129a0bd7abc6414cd16a","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.2.2-next.1_1665668489739_0.1190966469716499","host":"s3://npm-registry-packages"}},"1.3.0-next.1":{"name":"@ilionx/oauth-client-core","version":"1.3.0-next.1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.3.0-next.1","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"64c359d9679e4b44081551608677fdeaba799239","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.3.0-next.1.tgz","fileCount":113,"integrity":"sha512-4VO/4PtU3Nwmt5cPL+E/RSOis2KHr0Z4BJSQpxypFXfqLH+qumwVTGznTCOXmUmwkpGTZHugybBmPqUHrvFUHg==","signatures":[{"sig":"MEYCIQC3bfgtY2uQym1VBcpAjQA64uW9mkwY0IzGVKmYX1oPtwIhALSfQrVDXgUTQ/tHoEcoF4CduAy5u7jJfuzaaJKRmuy5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2557824,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjSTMkACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrXug/8CGj/nEWkrBbPK+aDS4eD3whUhbVNvdZ21USIGAK1nFeeaP7U\r\n2K/pFGYORH6QOqC4cjmxLUXIKGslIaywB1a2xI2O7/M21cyTgJpg/7Xp9q1f\r\n0zez/OZgQh1flO7CE3ot7DA8A28XUyUGG6S4mq/SoPzYx4k22WboQsdPaY1s\r\nCo4K3/fP3zmgx9P3fusulAAAcP341oH2PFIgcRxxkLkd97bMTm90NzjRAX97\r\np+I2Mpi5RptVDFe4sOXVgfduMiVsnDWGkVZ/IDVQzGQKI0ZBisYFGGroiHmu\r\nWI2adBak47l8VAE0oJsXsBEpzvhya1yH4u+PHTFJdXNBIikozHU5HtNFU7i2\r\nSS3F+oMl49IqSVwldPg5WtuWzxinQ+aWmwcMQOxJxUBZLQcs8VwytPwv1k6t\r\n5cdgs58k3AYxjypIadRqCosaS5QoVpBQaG8Y7csaotnPkYnYO/ZZDAiLUNzn\r\nl5tEGI6VUFMDmu4ncIxWQaGnzKt9Q6pJOHIYyn12HQ/keTQOZAQZlVZ11l5Z\r\nbhOBTFpHgAyMMwRbp0rmIpMQSIjQ1N07Wx1sudm48tK3M4fWb1oF8+LAfqPK\r\n+BLHwrSOt5zcH09j5Fk6WPUwBwVblRgjLaVZ8Sc3GzMZxXyxiCz6XJTUT9Pu\r\nIT2QJ7HZMaCwaFiOmMwHDyDKt8ZSGklP5Ag=\r\n=0jBX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"98be919108d2875ee5bf3312c376514d48bd3490","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.3.0-next.1_1665741604452_0.11682858115783223","host":"s3://npm-registry-packages"}},"1.3.0":{"name":"@ilionx/oauth-client-core","version":"1.3.0","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.3.0","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"09a6138e330e410333320b5d5bedf9e27052fced","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.3.0.tgz","fileCount":113,"integrity":"sha512-fqto/QRpRdTg81JrZHEynIsQ99fnNlzo4hntbW88jdPnz3MHscD2J68+sjCYz5vPvGTkq8y/Epb/cVdMnWhzkg==","signatures":[{"sig":"MEUCIQC9SRl2s0ngXR8JRQZOlEp3cpqhQcTqRTlHy7/OQEOHZAIgUYsLdxa5/XDL0X+OhZCua3sgswzUAt9WKu+uXNoJNKE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2558706,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjSUKTACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq+rxAAhOnxMrDa2hWYMjz/hAiopQPO5HmJt62Rbe4dRv2OQF0Kd7Bq\r\nIWXYTKaBxbPO2XXQSRUSzGRApYHbhKIgw7OxPdlINTrn8QCnCqr9sWhPi3p9\r\nh6gyla+drs6Lsw3CJtNTfHWSywvHKGozHFRWVI8K8EPotnlJ+zUSv/MEWo0f\r\nKT27i7S4aw6mXBb4g1DAjImkcysFBwPyqX77SfyLHhjPhJciXTiaJGfOS+wn\r\nHNMr5tqgUzzctvFhft0TBB1FQZ+RgijKlQcLBnpB9S4ZQH1TJoksMmWrehoT\r\n2ai9Oq2P3rVSCORaNWqEVwmSECZahAq7W1UvQlzANWgWTuWFWQHDb9wKWzgx\r\nNPsafQ2shkUXigzf3Axk6q5+HaRx73Wnnxqi1i1YkwNluK7uS2jkfz9AT57r\r\nA2iZ4n8CQFbHcoa58ThiWO2gbDBiXDAH5Io5opvvd1ca/j6uTGGcdlSD4XBT\r\nq0ngMGbcEtG8bO2bwdWqKVDa/YoskmTTonCcrrm9/U9oXKk21aQcgboLuY5r\r\n8wemc9ojT1HTB9tT9HPurFBgIXeZoZ2Z2NlDtGuDPew1L43uwyYUMLQtDRvV\r\ndzt4i3uxXyGR5u+9SbEQdOapoBARDB9QRTzofO8A9JzVIzhje0Y+mFqwB8Mn\r\nm7SK0CZYERTuCBSAbbTsHcHl+Fwd/91TZF0=\r\n=1j5n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"6b2ea4702b8305c91ae40342f98855847025c797","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.3.0_1665745555452_0.6111278479147926","host":"s3://npm-registry-packages"}},"1.3.0-next.2":{"name":"@ilionx/oauth-client-core","version":"1.3.0-next.2","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.3.0-next.2","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"91ac53421cb11d9d7e8ccfab543a8e5230abdb11","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.3.0-next.2.tgz","fileCount":113,"integrity":"sha512-U2kfCWDMdSlIFFxwIzh17nU1tSxhJ/mUPsVOABBT5n4pSvYjlsCuK40IvUj7D0D3jPmj+Ft4VyltSAbaAM53Zg==","signatures":[{"sig":"MEUCIQDXP2T34ElRFbCKEL/q62mYhGiRyl9GH1IruG3QmQ3EMQIgK9s0ffDExqWQIZvfJCzUCMPCkYV9BtT+5HrqNznZHZI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2559858,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjlyhJACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmr2Bg//SnVVPXoR8I/H4UoYZF8aKBl3VSrCMrvJr2moT1NHEOebsrk0\r\nXS5dCS2+M51a7cGq+6pYhLuKnct4YnwjkvnGj1LdFk00ateWLSuPPgBpZoM7\r\nJxzx3lsrrz0hmfg8QB9sGPTjaeLUBF18rdAmlPnEIrSxRR52gUDH1zK1GftS\r\nmBrF8Km8eFx6gkEm7RUsWCttrK5F2X5YsXCjJW0E1/gizAP/h3aUac5SC7Nu\r\nMPn7E+3Bmk27iqRgWLij2bUQeZ83ZAaEUVRSDYbxTOn8qtu1jT0seglRwMM8\r\nfyTwMvv1bu/fH/LOVVzw0Oicu8igXnjUbHQgQvW4luCHo1SMcvMGP8f6xdvL\r\nPbx3c7sWUJGO471a44z+8rz4pQV4VMxpGSLmWhn5ZB4nB1c7XimzglCcazSN\r\np6WkPQJb7o0LjdW8jn8Jv+SGyppd55soGCx5z/6d+MZ9LPqxXZ4caAdlHDbF\r\n+iCmRROoVQHgV0w+eUryr/Vjb2tzb55B7yrU/NgQr6g7zCjTZxDzMyvDHuRc\r\nruDafEvki0xDz6uzfBVxXwbLQfAijoXznBfyYf9ef35xxZ6oCXUhLYpUri0i\r\nZ1+vO78mdWmyBtH+U4I/MC165KyMI9JNf4bBc8qJmBtl66VD2SdulwcYz6u7\r\nRRM5Q+cxo8yzseyLUB5JWK+5qHhXI4mmg7E=\r\n=izHr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## Debug mode \nIf you want verbose logging from this package, you can set `config.debug` to `true` in your configuration. \n\nAnother option to enable verbose logging is to set a LocalStorage variable `oauth_client_debug` with any value.\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"2ac5f43ebfc5caba4f6abe3a1eac41e033e774ed","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.3.0-next.2_1670850633336_0.29287948477266346","host":"s3://npm-registry-packages"}},"1.3.1-next.1":{"name":"@ilionx/oauth-client-core","version":"1.3.1-next.1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.3.1-next.1","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"2669a267d93b4b28b52715cab81e0ac6af6f1157","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.3.1-next.1.tgz","fileCount":113,"integrity":"sha512-oQT+fvET6Qvg1w5Q8UWmoCAASjHWDXU2FrKOQniR4+fh1Xxn25ftWeGlUsK+RrF4jttsPxGe7pprbIBqs62XVA==","signatures":[{"sig":"MEUCIBpYYd7lxJ90FiLaQZqQpxLFctmudfdxZmv3kKSmUELqAiEAkQJ3WDRWAKIgiFMzcPg4HCeH0SegPjq0GlyPJ+RXOGg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2560267,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjlynqACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmruAg//e/Sd7Y3odqNDzWfwuflYCC4WtQg86+XHsMG5TjVBI5k1REjh\r\n00Zah13akYNrRx8A3oWS4lku/QQkUBZqpqapgdzi389lBMC5PPZZF8T+C7rW\r\nzWQDtoR3u0x4I2Q0QxP+uH8dAT/NZtYyZTjvBGrj7+t6Wud5yI9hYHaRu5+8\r\nBrShgZ1mFS/OcqAjiXs4rk3xUVV06MjIwoDaRV8YUH6h0svCf5WncRVZiB63\r\nJLcnN3YqyCcf49Y4xu8Dv/3xPZ9+4uE2OyZaRVWJjVB7BdSVqiOHvU+VAz6t\r\nPLazNVD/OfDjfiF+VGmNsmgCK/AhaPD2g9zGi5i0QE0fYb9ZChFhD6TYPBqM\r\n4sTQrbkidcaJt+PIGDgM8jEVOnNk7790fzgXQpG6j54Q26QVIlpnxmKiCj7W\r\nnUafWmz4BAEkSOoo04+iTelPC1Dwl/6DtCcrbytViJNTlHh7hRGsBMlM2HlR\r\nqeT33z5MDwvn9rUFaRSbIlutAp/m2uIYaTlmvIBAuadTWRdc1no8TH371vgN\r\nX/ysPMM2R8eVZLFAC8X+ldOrs/ALHtgQ0EhppDeczbJhNY46dFqXjf7KB8Rf\r\ngywxxQURbuRIkUwCauXD5zDBdUKpdBxJmxBQQ4DU+/QrGVZsHv473Cw0HkAz\r\nlgOMKLIq52mGpkYUDskAGOLniO5ELGHZQF0=\r\n=fwNk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## Debug mode \nIf you want verbose logging from this package, you can set `config.debug` to `true` in your configuration. \n\nAnother option to enable verbose logging is to set a LocalStorage variable `oauth_client_debug` with any value.\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"08fe9e26446b5e65d8468b5daa4094041dd85e52","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.3.1-next.1_1670851050298_0.01874947756679224","host":"s3://npm-registry-packages"}},"1.3.1-next.2":{"name":"@ilionx/oauth-client-core","version":"1.3.1-next.2","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.3.1-next.2","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"05a4c630629f55d6bac67b4631a1ffc5d8fb9fb9","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.3.1-next.2.tgz","fileCount":113,"integrity":"sha512-ozvc2G6JzFG+CmPr4yvFwA6Sh9Xh+UNcXhzmShxjZrZmbTYnsnM+85M+memt7jOIE5CvbBUQirCzTTZ06VWnag==","signatures":[{"sig":"MEUCIAapvEVgsA/7GGNRJ2tf4evX1nKIQpLLtiXgMNeYpgexAiEAoeCV52oQ/54nLyG30zmSUnijPfQEBBlymfGq4giRxoI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2558574,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjly9qACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpRfw/+PjFtq8r8i5xdgfUVi76Hxoo/sKzwlgwP2MFltce5zLrWushM\r\nsp9VLewsvE9yY4TNdC6a8aPYeHHSe4f0eGwzd79HyF0VoznOLcLMQmSHYwcP\r\nt9xbmLW2hx39patnDqnW6ZptwyRlHfA0s0GAZOGfZ+Ge2kZgf2bFSdD8E4IU\r\nVV1x0/r3fOs3LrVknO7bwOqlb5j+tBgxEl/bvzPovBl7OzR+lsCV/6xHqV+A\r\n8dPmTj13ip7aIhuMZ7ncC5ctPNmHhbXgmDX9S2rOT6ljOVmnQgCETcgjOjTS\r\nf8/9ZUwHAHDHwm7Rt4B94/PlsN2YG/DJF8bhf8FZxlxmspQ1Lko87Bofp70c\r\nlmLcCdoqZAjOutsi1N+TD+fRYzYW3QGgbTejJ/cbxrRnG/qSk0XotykBXLUO\r\nqqh76MKEQjIlVd0GRD/2oJrwIP7KIv237qlYZ02I6EQjh56y1jUEPmFl+SxN\r\nl1Ul6C3cEDlOiNpY0WzMylqLJ3EJAQMe7ooRDhKfI/ZXFqOzmKSd9Z3LolEc\r\nxEhAoZ5Jlob//mxUFhGe9i6dZkcUAhWbYjE/i+6aWttL1L98pIk3aGLI1Z9R\r\nUPAFn8bwFKWo8uBkr6E6f6vECw1fsUzuUD/0MOTMDKU+XlJcP2b8xlaQnkrK\r\nw0Bf9cDbbYVul+kvIHEW0AgroZYTSouO+uM=\r\n=X7TY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## Debug mode \nIf you want verbose logging from this package, you can set `config.debug` to `true` in your configuration. \n\nAnother option to enable verbose logging is to set a LocalStorage variable `oauth_client_debug` with any value.\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"fe86e9ac2408c247ef438a5998bd186af694d7fd","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.3.1-next.2_1670852457935_0.5614352444769972","host":"s3://npm-registry-packages"}},"1.3.1":{"name":"@ilionx/oauth-client-core","version":"1.3.1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.3.1","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"5f3b00b18307cfb0aa0b556fdce0cb987f7f4391","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.3.1.tgz","fileCount":113,"integrity":"sha512-EBNIjyzFKunQwI7hB3T9G4B/JqwXSg4iXq/UryY+8aJbh+Q/skgJ0RyqOOTq1XSoP8QPGcfuzAFWWwtI+iS8ig==","signatures":[{"sig":"MEQCIDhzZVLdgOcxoKpjsDj825LfszwLfbvF8F2S6Pw/dGCwAiALJER6TTjRX2CTQVdas0JcN/4xomCxbqIlhaKNOJ/K4g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2559188,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjlzS4ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrEvw//Q2+lN4CSoBX1K8bafkSnLuPM8EqZKRAJ6AWjFEDxBHoZXBJF\r\nwzkWhrNc4VOyreTQOiPolOhpm+lAWARGAwXFfcPRJZFbWtZbm9u0y2KtZdUP\r\ngYSjef2VZ4k7zysl03k8avDQFvbKfIExEvVHVEWTbWb5/rDZzASrmMjFJews\r\nllUSwu14pDAYLaYL40AZn6d8pjKM0FktCAVv0rPp2w60MbDZfY2yMyGqKjyH\r\nLvCB0as8pbxHq4GTZPTBTCp0oGkluA9i1NUryQkOsQNAvYe+5zu2yV8GXGMN\r\nb28elIgqqTsvRe05n33U2YxSm0+hCpqvm/KWJQgM+sSH7+qsgLgB/doAheBY\r\nMh7K1K2L5Vqr7h2ELbrEVMt3wad1Bfp9XyNctK3UabRDlu3NqAIp5I0vldjr\r\nHgo1AsNZ2B62/ni22bj64oqzcRaEdrCiqc3sgn+NkVEiTHDyw8rLulDdFsHh\r\nAfdBtGj3fFxCODI/Dod3x+z/R59/O1qB34KgulFNKTi9o9t39WArA0YIbJoj\r\nJ2uLSXLBwmK5/umR0dV0ZMrA+7yBol6zfVAtwnR5V0934xxpuWH7aU/TmLa5\r\nmLIQ9Sjant9Yz3Dhs9X+IEz6CPU3YS8Jx382kvWfYSX1JNmKWnZ2QPhP+bjo\r\n1OugZLNukwTr8ZNYKLxhEB4K3Ne61zSO88Y=\r\n=MTmG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"8f0af3b2363dc5e7966e3317805f813e5683971f","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.13.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.3.1_1670853816229_0.07887170898450391","host":"s3://npm-registry-packages"}},"1.4.0-next.1":{"name":"@ilionx/oauth-client-core","version":"1.4.0-next.1","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.4.0-next.1","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"81d99ac71f95ececf553d24bff3062c2e3962184","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.4.0-next.1.tgz","fileCount":113,"integrity":"sha512-oRihaWlNeZrs07GJs6j6P5OQj3iBHr2C6n7ZE9OJ0VookhpRvSPtZWl9or2jJO7ofXGZl/kz67yN3iNcapam/A==","signatures":[{"sig":"MEQCIE7s1YayPgkRz/ptxHOAMQcLIZoXTdxhZGOQbp6EM+PzAiBskI2nDCffflNB9mb+HqpQDOk18YT7lLcUv19MjIkLpA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2559625,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkALq/ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrrMBAAiv8oRm2Ts7WCLQKf+kde2OhJAGxJuQfzRG9IEHfMPd1gFXIZ\r\nigklMcFWGCL2/c/rI8Xmqpx0qeXrwhBSr07BQgWnJULXiq84TkyUuRrT/Lck\r\nUGYlTMkZQdKhukDCbRZUpmi9mY9FQLfU1c5Bk7wXc30aULpLJHTO8ohnpwFR\r\ngpFChvPQXkordRajqTty4z+YlI3QtRtX1OuvrbT69Y+SncLWfGOM8LcwPNcY\r\nnRvtdlYppv94pJwJOKv9FKJG/WN6zqbEhHCzXAyBR5SbthTGbwBVieahfOt3\r\nwTb87tnio6p9FC6BgDz7eiASOaxbJx+EUjAk9KsaMDb4oR+Z9V6rak7Xqd8m\r\n9G9J/Bxk3FjAKRmCFF6bGn4CpauUyq3hJJ1nDLl835mYVxekd52vOGh3PWWl\r\n34b+0nD9yLkVvJCk8k4js2NHDjpcOH+LPDDEj+HI7STq9DyGLLwLVNO1XNdt\r\n04/T1sIDS5sFiaWbg+vjIV0WL2YmKqIbs81Z1kgYLSfivgSBoyzaoJEthYQO\r\neKuDVi47TaHGlFjJBmoLsIx87GjsOr09WcEH2cWaIMgV3/xhykdkx/kobSfc\r\nrQixS6Nx43lRUQwL68YRQxKcFwnJ0EthDT6jeOvwhEhDcYsT5jf7ivhrkCQD\r\n/+yz3kWFwe9tGBGWb46nzGgwoN9vfvbRUK4=\r\n=Sb82\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## Debug mode \nIf you want verbose logging from this package, you can set `config.debug` to `true` in your configuration. \n\nAnother option to enable verbose logging is to set a LocalStorage variable `oauth_client_debug` with any value.\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"84af5ae5a3ce551469a50e1ae02ab3ed8b23afb9","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.4.0-next.1_1677769407247_0.4371687219692675","host":"s3://npm-registry-packages"}},"1.4.0-next.2":{"name":"@ilionx/oauth-client-core","version":"1.4.0-next.2","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.4.0-next.2","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"cada6f9e125c61c6954cbf402db5282fc70f3ef8","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.4.0-next.2.tgz","fileCount":113,"integrity":"sha512-/f+CTz8+cxj+DQNARq744hZp59mtIsNOvfrgQ9Nq50/DymqKYgohAg7e5cw9f1T+fjhAFAq6cVkAq9Te0YqtcA==","signatures":[{"sig":"MEUCIQC1im0N/Ws8yZ6PDYpmo31ugc6g5WP6PoVeXIewgKD7vQIgMaKWyVTz8rYnbJmExRQ+ndC2GMNLo5/2AOeeOjkVz3o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2559871,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkBef9ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpV3w//bFpNMcbSWrQXuhMvV9SbLiXm4uHYyCaJE6AN8uMshZ61v+Ch\r\naCcD7wzoSoVAfjIZ3ji22WybU/cL6ZezKbHDy4aU/2WUGLSbkS4qQGKMy6SP\r\njMvbvAoKKpzC9LfJPDyzp46985DG5IZRa7e5qcYfpw1uFqdUPAWOMqvv0Yuq\r\nvR3nli/Qov1CWe/QOso0G2hBIYaJB9wZ2ZlaedIA5Fl3clM2RomChi38wOfz\r\n56swjgpSGt4Kh+kM8PXEibTRetrcfBx0hHXeEOkvT358VHoj+rl1zw0rsszd\r\nzqGtIigeQNbSRWzxxr12eyKGD7IejFHCIG2eirMm510mCqP3i0BsWuvArs+w\r\n7ljspBk/XamiNF8JbORXJ6pfzLqle9BJq4BVFOZROF34DyHnLz+axehsHFtJ\r\nR8zJH25HWYro20KLZGy3eAR7zqG83nSTlA2eHufDO+K4pnfi5wEGc0HnNlLZ\r\ncRNblNdCPXbV7NeW/to+eXCPwF6HWL5QPfuFr1x2qmxTCunhIzUnEVgiRZj0\r\n3ADudRdtRJyC5nFx6quXzsHBcEENeqoILhfF/c/BOX3tNB6p95ch6ITsPU5J\r\nLt52BnslPxOtbWxixf3aflLvsSt79i+k6aHoUb2+ugsJCfA1OHvPQpfRoLHQ\r\nqH5tIvrom9rmhBiKpEnMREo05EKUxFCMMz8=\r\n=E4Eb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## Debug mode \nIf you want verbose logging from this package, you can set `config.debug` to `true` in your configuration. \n\nAnother option to enable verbose logging is to set a LocalStorage variable `oauth_client_debug` with any value.\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"949439ed4e29f03fea6fa63174b20b2abebb8062","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyCleanHashFragment && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyCleanHashFragment":"cp ./clean-hash-fragment.js dist/clean-hash-fragment.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.4.0-next.2_1678108668976_0.8251895094881889","host":"s3://npm-registry-packages"}},"1.4.0-next.3":{"name":"@ilionx/oauth-client-core","version":"1.4.0-next.3","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.4.0-next.3","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"2b831d64b79aac279214ba77c3272b418f1338d0","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.4.0-next.3.tgz","fileCount":113,"integrity":"sha512-53HeKojZRiTE3cRHDfV43bAMeT2yYTEV4SehAlXAyuJjZ7kG/UzoHBfmkiPqKWLP77oA1/YPX45EjTvwGN3rUw==","signatures":[{"sig":"MEUCIQDlhYkit/8vxyDyvCgrbyjORqF6wKGMFl//ZuMkPGXnFwIgKrLvnJK9xrRcQqyIMfRxw4l5Y+iBLDBN8kGQuuGP3IA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2564611,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkB0MLACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrDyQ//YURthbDFeIyNO9wY4Qn0WGscjaicpaa3JFUfGVmpCKP7DfLW\r\na7QktKEBJHiEowzXeBhW42epHYF63phj+Xy1dqAPfMosfCI8dRND15/Yece7\r\npzKX2HryqlYmHF5MZHL6aPoE0mOnKEJwPk2BLi90/tZP+R4koljzgY0tP8We\r\noivvPlEd3j4etkPSGXvapNB/mSHpBw92ZxT1KAdqOy2g15LgZNUMhsMzp4fK\r\nrWepn43kfkI66FK2Kv4eLXoa77F73KhO2Q8X93sv7+2iCjSi03YuBylRrWI/\r\nplU4JBiVmhp6M7UjYjVyRMsV2V95j+arv0MELkw9SkJ8bItMGi/MfYpCmB4H\r\nmqbh0sg74eon2mFlY4ZSN3+jef3QsjEMryWGzhEK811HOQRozZ1uEdjheZjM\r\n2x8VnICU4loUpaOEdb+Q5vS7ZQDXIb7YIQbU5UhQ5jLv8hBXI2AiTCsg0/1W\r\nAT+nNRb81BAB7xVd8gmNIqzao+RCBpeQzqGELESnP9qL4p7YbRFO2Q2hFHs0\r\niUx4JiGXdjpY9BvxFlXm3YMZe/kalyLVsJrZeXl6P6uMpVFk5/ztfEa6Qnrt\r\ncsiGOd6QqWiliHktnnSDXHdXVBwEqg9KFMUhHg/W8R7ptEVplvP+E+1ceoL6\r\nGW0TWw1K+XG2GLK0xhxpZ2amjCDGKRDn5NE=\r\n=uhHN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## Debug mode \nIf you want verbose logging from this package, you can set `config.debug` to `true` in your configuration. \n\nAnother option to enable verbose logging is to set a LocalStorage variable `oauth_client_debug` with any value.\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n\n\n### How do I prevent tokens leaking to third parties?\n\nIf you are using some kind of analytics tool which registers the url, you want to make sure no access tokens or authorize codes are leaked to third parties.\nTo prevent this potential security risk a javascript file named `pre-clean-url.js` is bundled within the package. Link this file in the `<head>` of your html file and make sure its the first script that gets loaded. What this script does is store the hash or code in the sessionStorage and removes the parameter from the url. That sessionStorage item is later picked up by the package as if it where the url.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"7f6bb701af8637bc8027ad8505cf346a2259c7de","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyPreCleanUrl && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyPreCleanUrl":"cp pre-clean-url.js dist/pre-clean-url.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.4.0-next.3_1678197515622_0.31775400046030744","host":"s3://npm-registry-packages"}},"1.4.0-next.4":{"name":"@ilionx/oauth-client-core","version":"1.4.0-next.4","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.4.0-next.4","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"0f863c02c49e1fac1b20970903aeb8ec51f03c29","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.4.0-next.4.tgz","fileCount":113,"integrity":"sha512-oxb4HpXd4otk0eszhM1vUqYKO3WaDnubZVvcN6dWVv4cV1uCnnA9u6uMAsGX7N5liauANI8KGYsxLtR3kzSFIQ==","signatures":[{"sig":"MEUCIBJ518H6u/+9yWp/RspR85+PAjFqBig8a02swNxcPdbXAiEA1UBXg0yg0eP2Zq+6WGImJMZ8TRmC3cKF3whHFh0zb4I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2565456,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkB0wqACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmrl+Q//Rh7jLh23Sb203k0+F6xiLgIhIZgd/O1aR7zwgseXi4idzA2O\r\nskEOkYKumCTkjByYxzJ+vbvc+Kw5Acg/PM2rrpJfPzoO8EzqWq4TmBulwV9M\r\nfaE03BcfPTr12f0vYUQPe5nbc960WSgCfAXv5Tpol10FuLZ2nG1u5VPwRQwN\r\nVBc0A/lv8QpMpMu4UKvezye33udySxpRgx/LuWyICfEXNIE6CrVIT4sLQ9zM\r\ntXvC2pn93PoFpw6P+/4oevG4w5UOVF5HCeNTWG+Bj54/1oE+Wty95UpKVL4R\r\nLB9gFU9JR0NGWUecp8fx7RfCwXDO497dX8a8RaWjTru3wLTU+IGepYVEdJX+\r\nJR2Y+NWMfmz4mnfe1971y7mbgjO0jlOuj95xq0qwxjmDG5G81GSbsidQ+fyK\r\nnAFSYBxJbRphkCtf6E3rPfgdEXPCYrda5IGqWS8jo66zlkA5yGTIAUaif+v3\r\ncVzhGSg9f+xEMqyuQJdLfu/6IZ611vFNMRQWZxUZX0QTcRgfrt5W8XsxbylJ\r\nqK/Xg+HoLw3Dc0dVd2r1lskSx/bPMd8FdlguGXai4X7fPm3jYxoZDPiG3H8c\r\nN/IUafVFKYaoCQeKGC8Gq0Cp/XfuFRSVjaN4RKGqwl00NaxIWhKRfQ0nc4PO\r\nqbsFoToay7sZ8NH9tyPTUmxsAJcI+B3OxeE=\r\n=faNE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## Debug mode \nIf you want verbose logging from this package, you can set `config.debug` to `true` in your configuration. \n\nAnother option to enable verbose logging is to set a LocalStorage variable `oauth_client_debug` with any value.\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n\n\n### How do I prevent tokens leaking to third parties?\n\nIf you are using some kind of analytics tool which registers the url, you want to make sure no access tokens or authorize codes are leaked to third parties.\nTo prevent this potential security risk a javascript file named `pre-clean-url.js` is bundled within the package. Link this file in the `<head>` of your html file and make sure its the first script that gets loaded. What this script does is store the hash or code in the sessionStorage and removes the parameter from the url. That sessionStorage item is later picked up by the package as if it where the url.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"e6e54c6422a1d538366dcc1d4d1ada9470e7c9f9","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyPreCleanUrl && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyPreCleanUrl":"cp pre-clean-url.js dist/pre-clean-url.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.4.0-next.4_1678199849968_0.8807905725470533","host":"s3://npm-registry-packages"}},"1.4.0-next.5":{"name":"@ilionx/oauth-client-core","version":"1.4.0-next.5","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.4.0-next.5","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"66e4567d3b7be3c07bfeb6ae46297e7aa27077b6","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.4.0-next.5.tgz","fileCount":113,"integrity":"sha512-mcn37Zj3qZo7az+sr2VOxSZRhcedSvSOTfEac83RHCeI+h2AFp4XMRJTmDMZYw0qAtU6M4E6IJKw+izFs1uaMA==","signatures":[{"sig":"MEUCIQCUFFdPfwhel0clKIrSM48L32V84MgSDO/SF9tQLzclJgIgHRI5K7RyO4ubEVff1uaMCpC1mvu2Vfqezb6BPi5bWrY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2566466,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkIZx6ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq80g/+J1f+qIPKrYNzNCfca60M5atn06FuWuaSYx/C3kygdi7rhRLR\r\nvhhx5npwjUdlsBaZmDM/5zOui8e3CRKg8JrffkICC+eLeVHR81u4yMvKMeMa\r\nSfoI97gjKb6kc9fferGmyLcyV9kXX9mHLsGy8lwysrPc/6EZqJE7okgvktgy\r\nTYucgbo+5TDSyhK2LgWnj2MW4+1ZwZOL+Ti5ailL2SXMdGvG/chnhsKHlEq7\r\nJCRDOTIVRI8J49MYKOM+A0X4Evb6TEfTQt3EM9EZ1GmAn1X6kKv6WjzwKeID\r\n9LDJZEIyZUMSYKQ8g9/KcDCEN4yzuKyW7MwQcx6BOm2dY0tYALko9G4qBqPp\r\nUUi4EnOkSvWALJx2Z3rVrvr7enKFbkDnijEKEi8OQEIXy8sao+OTsxIjEewS\r\ncRluZP4qkAIm3mbQNPrt12H8gawazjYFfqNnSbSB1n/XYZWEti10WA9TVFVx\r\n5KZv9PkBa00IiLA+gK867nMhreOmDpNgr65WY+1OAKWBYJVtIyyv8DXbc83n\r\ny5C3ivBfxtc26jE4FLXJyh8ifDwdKPs8fla3NM2zU9cB4yCfLuSqDNYYSk4b\r\nu1e1T5aE2H6y3HeiJVNWpx9jVJHsGB9BsbN92U5aWNnNDczIezmxoUSCyh7Y\r\n4KUltP0ViVkmsizvBOmSIPuaQJwqAdWUPLM=\r\n=g8VZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## Debug mode \nIf you want verbose logging from this package, you can set `config.debug` to `true` in your configuration. \n\nAnother option to enable verbose logging is to set a LocalStorage variable `oauth_client_debug` with any value.\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n\n\n### How do I prevent tokens from leaking to third parties?\n\n**clean-hash-fragment.js has been refactored to pre-clean.js**\n\nIf you are using some kind of analytics tool which registers the url, you want to make sure no access tokens or authorize codes are leaked to third parties.\nTo prevent this potential security risk a javascript file named `pre-clean-url.js` is bundled within the package. Link this file in the `<head>` of your html file and make sure its the first script that gets loaded. What this script does is store the hash or code in the sessionStorage and removes the parameter from the url. That sessionStorage item is later picked up by the package as if it where the url.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"825aa147217f227f625206a52b89c8bd1ece8688","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyPreCleanUrl && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyPreCleanUrl":"cp pre-clean-url.js dist/pre-clean-url.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.4.0-next.5_1679924346460_0.2255229284221265","host":"s3://npm-registry-packages"}},"1.4.0-next.6":{"name":"@ilionx/oauth-client-core","version":"1.4.0-next.6","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","_id":"@ilionx/oauth-client-core@1.4.0-next.6","maintainers":[{"name":"lschna","email":"lschnabel@ilionx.com"},{"name":"jkockelkorn","email":"Jkockelkorn@ilionx.com"},{"name":"raymond.sanders","email":"rsanders@ilionx.com"},{"name":"meesvandongen","email":"meesvandongen@gmail.com"},{"name":"kevin.riemens","email":"kevin@xclusivemultimedia.com"}],"homepage":"https://github.com/Q24/oauth-client-core","bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"dist":{"shasum":"6ce3edcda66bb88fe4239979310e818b764ad095","tarball":"https://registry.npmjs.org/@ilionx/oauth-client-core/-/oauth-client-core-1.4.0-next.6.tgz","fileCount":113,"integrity":"sha512-SXZvr7AWVSu2AN2cQ98ogierygtANfyUhR0hpUYUsXMBy68Q/2AT2YGGiYOi1n26xBhK24DLdPxDBdFT+AP/fg==","signatures":[{"sig":"MEYCIQDJuzvCHPSh77mP/+4Akhk2vHdGMJ8JtgQauyUsM9c8UAIhANpWrGMg+1/UOyXBpdPXZkdNGkW+UXh/Y0H2MFbMYGYm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":2568930,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkJqeTACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqgQQ/+ILBdZT7dKY8UaA9tFVD9+I+Gt8j51jdFkfRmbtEygVB70Kra\r\nVN2X61JgLspGfrqiMgPiz02bwbYnOEChl0d9PzJV1V8A8Uwe6RfxioO1EYmS\r\n6uNWHkxsqsB8+LZiif9nnkvt6VvevORDmNK5qKHwl513BDZYIBuhx10eBJpc\r\nfST9bA7F/HMSrWpLCnYglQUxmHK86cA7hCvZb5eDo0U2mCY0oDCOER6HeQVh\r\nshshOJdLRT1ach7rROJOmXf1Re4iV3i09A8AKU3Y/cDVtlTyo1LbyPaQLA8t\r\ncVi0TQPPS6Nd+OiYLBS7YLzAd53LcF2savv8rSqGSnShvy4uDKu5mU/v6gt+\r\nLev3+TLRLYXnoFDxGrXh/jo5pMTsKTyMZw54JpiTFY/X4YZki0ftMcGsr4GL\r\nSg1fPrCtD8OXl8yyDvpRaGvyK+N/UgQxndHzXY/T3PnCPIQvlgV8OjMYEHPu\r\nZE8Fthu8iwYih8K+oPBkh8A8Jozih0Y0np0b9lPEOQOQofT7bB9p3q+y/gEf\r\nZlb0HEz1Z2EhqOZEP5gar590e1F//qyRl8t+cAKNoDeaMMS8W4iD7Ow3nH5I\r\n2TvQ6zMLj7KPA44Be8iSz1tFvTNvDTb+SohuRWsYN3ExL+C40hLbJ4pJ2WBM\r\n/yP7NctashZAFyOfb1+7bomTi3PgfE0eRfA=\r\n=Q1qA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"dist/index.js","email":"info@ilionx.com","unpkg":"dist/index.umd.js","module":"dist/index.module.js","readme":"# Oauth Client Core\n\nThis library implements the [OIDC implicit flow](https://openid.net/specs/openid-connect-implicit-1_0.html) / [OIDC Code flow with PKCE](https://openid.net/2015/05/26/enhancing-oauth-security-for-mobile-applications-with-pkse/) for use in a front-end web application. The library can be used directly with any framework of choice. While it is not strictly necessary to use wrapper, there is one [available for Angular](https://www.npmjs.com/package/@ilionx/oauth-client-angular).\n\n## Features\n- Implicit Flow\n- Code Flow with PKCE\n- CSRF Tokens\n- Code samples\n- OpenID Connect Session Management\n- Authetication using redirect\n\n## Roadmap\n\n- A future aim is to have this library certified as [OpenID Relying Party] (https://openid.net/certification/#RPs).\n- Add support for [all request parameters](https://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters).\n- Move to native Web Crypto API when IE11 support is not needed anymore.\n- Authentication using popup\n\n## API Reference\n\nThe API reference can be found in the `docs` folder.\n\n## How to set the OIDC Config\n\n```ts\nimport { configure } from \"@ilionx/oauth-client-core\";\n\nconfigure({\n  authorisation: \"\",\n  client_id: \"\",\n  ...etc,\n});\n```\n\n## How to set the Auth headers on API requests\n\nTo access resources, a request may need to include authentication information. This is done via te authorization header. Take caution that you only include this header in requests for protected resources; simply including the header in every request is a security risk.\n\nIn the following example, we use axios' request interceptors to add this authorization header. With axios, you may create various axios instances with different interceptors to deal with this. With other implementations, you may need to include headers at a request level.\n\n```ts\nimport {\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport axios, { AxiosRequestConfig } from \"axios\";\n\nconst setAuthHeader = async (\n  config: AxiosRequestConfig,\n): Promise<AxiosRequestConfig> => {\n  const storedAuthResult = getStoredAuthResult();\n\n  if (storedAuthResult) {\n    config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n\n    // For info see Token Expiration section in Readme\n    if (\n      (storedAuthResult.expires || 0) - Math.round(new Date().getTime() / 1000.0) <\n      300\n    ) {\n      silentRefresh();\n    }\n    return config;\n  } else {\n    // The check session method will either return\n    // that the user is indeed logged in, or redirect\n    // the user to the login page. This redirection\n    // will be triggered automatically by the library.\n    const isLoggedIn = await checkSession();\n    if (isLoggedIn) {\n      config = await setAuthHeader(config);\n      return config;\n    } else {\n      throw new axios.Cancel(\"User is not logged in\");\n    }\n  }\n};\n\n// Add a request interceptor\naxios.interceptors.request.use(setAuthHeader, (error) => {\n  Promise.reject(error);\n});\n\n```\n\n## How do I check if the user is authenticated?\n\nOn a page level, users should be redirected if they are not authenticated. For this, you can use the check session method. Do note that this method returns a Promise.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function processProtectedRoute(): Promise<void> {\n  try {\n    await checkSession();\n    // We may proceed\n  } catch (error) {\n    // Under normal circumstances, we will never get here, as the\n    // checkSession will already have redirected us to the login page in\n    // case the authenticated fails.\n    return;\n  }\n}\n\n```\n\nOn a component level, you need to make sure at least a auth result is stored\n\n```ts\nimport { getStoredAuthResult } from \"@ilionx/oauth-client-core\";\n\n// If a auth result is stored, we can assume the user is logged in.\n// This call is synchronous and will as such not influence rendering the page.\ngetStoredAuthResult();\n\n```\n\n## Token expiration\n\nOften times, the access token is set to expire after a certain period. Before this period is over, we can still request a new token. The renewal of a token should only take place as long as the user continues to use the application. If the application renews tokens without paying attention to user activity, it is a potential threat to the security of the user's information.\n\nA common way to detect application usage is to hook into requests that have been authenticated. These are either requests to the backend API (i.e. requests to protected resources) or front-end navigation to routes that require authentication. In addition, the necessity of creating a new token must be considered. If the expiration time is still far in the future, it should be decided not to renew the token yet.\n\n```ts\nimport {\n  AuthResult,\n  checkSession,\n  getAuthHeader,\n  getStoredAuthResult,\n  silentRefresh,\n} from \"@ilionx/oauth-client-core\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst refreshTokenAboutToExpire = (authResult?: AuthResult) => {\n  if (\n    authResult &&\n    // The expiry time is calculated in seconds since 1970\n    // Check if the token expires in the next 5 minutes, if so, trigger a\n    // silent refresh of the Access Token in the OIDC Service\n    (authResult.expires || 0) - Date.now() / 1000 < 300\n  ) {\n    silentRefresh();\n  }\n};\n\n// ==================================================\n// == SOMEWHERE IN THE ROUTER AUTHENTICATION CHECK ==\n// ==================================================\ncheckSession().then((authResult) => {\n  if (authResult) {\n    // If the authentication was successful, we request\n    // a new token (if it is about to expire).\n    refreshTokenAboutToExpire(authResult);\n\n    // Returning the auth check result here...\n  }\n});\n\n// =================================\n// == SOMEWHERE IN AN API REQUEST ==\n// =================================\nconst storedAuthResult = getStoredAuthResult();\nconst config: AxiosRequestConfig = {};\nif (storedAuthResult) {\n  config.headers[\"Authorization\"] = getAuthHeader(storedAuthResult);\n  // After adding the headers, we request\n  // a new token (if it is about to expire).\n  refreshTokenAboutToExpire(storedAuthResult);\n}\n\n```\n\n## Login\n\nThe login consists of two steps. Step 1 is to send authentication data to the server (_username_ and _password_ and _csrf token_). Step 2 is processing the response from the server.\n\n### Sending authentication data to the server\n\nMost of the time, it is not needed to create a custom login page. The default page of the CIAM server can be used. The client id can be used by CIAM to determine which login page should be served.\n\nIf you are going to create a custom CIAM login page, you need to make sure that besides the username and password, you also send a Cross Site Request Forgery token (csrf) to the SSO server. This can be obtained with the `getCsrfResult()` method.\n\n```ts\nimport { getCsrfResult } from \"@ilionx/oauth-client-core\";\n\ngetCsrfResult();\n\n```\n\n### Processing the response from the server\n\nAn auth token will be present in a response from the server after a successful login. This token must be stored on the user's local computer. The auth token is present in the hash fragment of the redirect url from the server to the client. So, you need to make sure you will not clear the URL before saving it locally.\n\n```ts\nimport { checkSession } from \"@ilionx/oauth-client-core\";\n\nasync function calledWhenTryingToAuthenticateUser() {\n  // The check session method is used for both checking if the user is logged in\n  // as well as saving the access token present in the URL hash.\n  // After the URL has been saved, it will be cleared.\n  await checkSession();\n}\n\n```\n\nThe current implementation of the redirect from the server only goes to a single URL. This means that restoring the user session (the url where the user was before logging in) is a responsibility of the front-end. Take into account that routes which do not require authentication should not call the check session function (as it will trigger a login).\n\nBecause the check session function is used both for the login check and to store the token, it is recommended to do a front-end redirect where the URL hash is kept intact before the check session is used to store the token. This way the user is sent to the correct route after login, which calls the check session anyway (to check if the user is logged in) and consequently also stores the token.\n\n![Login flow](images/login-flow.png)\n\n## Logout\n\nThe logout form needs a **logout endpoint**, a Cross Site Request Forgery Token (**\\_csrf**), a URL to redirect to after the logout has succeeded (**post_logout_redirect_uri**) and an ID Token (**id_token_hint**). The form can be submitted in an automated fashion after all inputs have been set. If the _\\_csrf_ or _id_token_hint_ cannot be resolved, an error page should be shown.\n\n```ts\nimport {\n  config,\n  getCsrfResult,\n  getIdTokenHint,\n  getStoredCsrfResult,\n} from \"@ilionx/oauth-client-core\";\n\n// The LOGOUT_ENDPOINT can be requested from\nconfig.logout_endpoint;\n\n// The POST_LOGOUT_REDIRECT_URI can be requested from\nconfig.post_logout_redirect_uri;\n\n// The CSRF_TOKEN can be requested from\n//  Synchronously (try this first)\ngetStoredCsrfResult();\n//  Asynchronously\ngetCsrfResult();\n\n// The ID_TOKEN_HINT can be requested from\ngetIdTokenHint({ regex: true });\n\n```\n\n```html\n<form method=\"POST\" action=\"LOGOUT_ENDPOINT\">\n  <input type=\"hidden\" name=\"_csrf\" value=\"CSRF_TOKEN\" />\n\n  <input\n    type=\"hidden\"\n    name=\"post_logout_redirect_uri\"\n    value=\"POST_LOGOUT_REDIRECT_URI\"\n  />\n\n  <input type=\"hidden\" name=\"id_token_hint\" value=\"ID_TOKEN_HINT\" />\n</form>\n```\n\n## Automatic logout\n\nIf the session is closed due to inactivity, the user must be logged out to protect the data still on the local computer from access by unauthorized parties. After redirecting to the logged out page, the authentication information will be removed.\n\nIn the case a user may still be logged in on another client, they should not be logged out. This is what the isSessionAlive call is for. It checks the server to see if the user is still logged in somewhere. Logging out would also destroy the session for other clients. This would cause these users to eventually be rejected when requesting a renewal of the session, even though they might still be actively using the session.\n\nThe `isSessionAlive` call does not count as user activity, and will as such not lengthen the session.\n\n```ts\nimport {\n  getStoredAuthResult,\n  isSessionAlive,\n} from \"@ilionx/oauth-client-core\";\n\nconst autoLogoutInterval = setInterval(() => {\n  // Get stored auth result either returns a non-expired token or null\n  const storedToken = getStoredAuthResult();\n\n  if (!storedToken) {\n    isSessionAlive().catch(() => {\n      // If we are not logged in, no expired check is needed.\n      clearInterval(autoLogoutInterval);\n\n      // Remove user information that may exist next to the auth information\n      clearUserInformation();\n\n      // You may set a session restore URL here to be used on the\n      // login page.\n      setSessionRestoreUrl();\n\n      // Navigate to the logged out page via the router.\n      navigateToLoggedOutPage();\n    });\n  }\n}, 15000);\n\n```\n\n## Logged out page\n\nSee the FAQ for the difference between a logout page and a logged out page.\n\nThe logged out page is used to show the user that he has been logged out. Next to this, it should remove local authentication information. This includes local storage on the current domain as well as cookies on other domains via a logout pixel.\n\n### Clean up\n\nRemove possible left-overs of the previous session.\n\n```ts\nimport { cleanSessionStorage } from \"@ilionx/oauth-client-core\";\n\n// Upon opening the logged out page\ncleanSessionStorage();\n\n```\n\n### Logout pixel\n\n> ! This is an opinionated way of handling logout.\n\nIn case there multiple domains that need to be logged out at once, you may choose to include logout pixels. With a logout pixel, you include an invisible iframe or image on the site which is hosted on another domain. The embedded element contains some logic to logout on the hosting domain. In this way the same domain policy for cookies and other local ways authentication data storage can be circumvented.\n![Logout pixel flow](images/logout-pixel.png)\n\n```html\n<!-- As iframe -->\n<iframe\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n></iframe>\n\n<!-- As img -->\n<img\n  src=\"https://example.com/logoutpixel\"\n  width=\"0\"\n  height=\"0\"\n  class=\"hidden\"\n/>\n```\n\nIf you are creating a logout pixel, you need to:\n\n1.  Make an array of all access tokens in the session storage\n    1.  Call the logout endpoint with each token\n    1.  Remove the token from session storage\n1.  Remove the `_csrf` token from session storage\n\n## Auth Result Filters\n\nIt is possible to write a custom filter for the (stored) auth results. This validator will be used to get a valid result from the stored results (a list of all previously saved auth results). This is useful if the auth results you are using have some non-standard behavior.\n\n```ts\nimport {\n  getStoredAuthResult,\n  parseJwt,\n} from \"@ilionx/oauth-client-core\";\n\ngetStoredAuthResult([\n  (authResult) => {\n    if (authResult.access_token) {\n      const accessToken = parseJwt(authResult.access_token);\n      // The backend is creating special tokens which have `someCustomProperty` set\n      // to an expected value. We need to validate this.\n      return accessToken[\"someCustomProperty\"] === \"someExpectedValue\";\n    }\n    return false;\n  },\n]);\n\n```\n\n## Debug mode \nIf you want verbose logging from this package, you can set `config.debug` to `true` in your configuration. \n\nAnother option to enable verbose logging is to set a LocalStorage variable `oauth_client_debug` with any value.\n\n## FAQ\n\n### What is a silent logout?\n\nWith a silent logout, you are logged out in the background. This means that you are not redirected to a logged-out page. However, the access token will be invalidated. You would use this when you need the user to be logged out in order to perform a certain action. If you are going to use this method, be sure to clean the session storage afterwards.\n\n```ts\nimport {\n  cleanSessionStorage,\n  silentLogout,\n} from \"@ilionx/oauth-client-core\";\n\nsilentLogout()\n  .then(() => {\n    cleanSessionStorage();\n  })\n  .catch(() => {\n    // Handle errors when logout has failed.\n  });\n\n```\n\n### What is a silent refresh?\n\na silent refresh, a new access token is fetched in the background, without user interaction. This also extends the lifetime of the session with the Issuer. An error is returned if an End-User is not already authenticated. For more info, see the [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n\n### What is the difference between a logout page and a logged out page?\n\n- The purpose of a **logout page** is to initiate and authorise the termination of a session.\n- The purpose of a **logged out page** is to show the user that he has been logged out. In addition, the **logged out page** is used to remove authentication information.\n\n\n### How do I prevent tokens from leaking to third parties?\n\n**clean-hash-fragment.js has been refactored to pre-clean.js**\n\nIf you are using some kind of analytics tool which registers the url, you want to make sure no access tokens or authorize codes are leaked to third parties.\nTo prevent this potential security risk a javascript file named `pre-clean-url.js` is bundled within the package. Link this file in the `<head>` of your html file and make sure its the first script that gets loaded. What this script does is store the hash or code in the sessionStorage and removes the parameter from the url. That sessionStorage item is later picked up by the package as if it where the url.\n","source":"./src/index.ts","exports":"./dist/index.modern.js","gitHead":"0f4b985d7a0de8fbc975e2895a0d53c557cddc65","private":false,"scripts":{"dev":"microbundle watch --name OAuthClientCore --external none","lint":"prettier ./src/**/*.ts --write","test":"npx jest --watch","build":"npm run bundle && npm run copyPreCleanUrl && npm run typedoc","dev:2":"microbundle watch --name OAuthClientCore --external none -o /Users/meesvandongen/conformance-suite/src/main/resources/static/oidc-test-client/dist","bundle":"rm -rf dist && microbundle --name OAuthClientCore --external none","typedoc":"rm -rf docs && typedoc ./src/index.ts","prepublishOnly":"npm run build","copyPreCleanUrl":"cp pre-clean-url.js dist/pre-clean-url.js"},"typings":"./dist/index.d.ts","_npmUser":{"name":"meesvandongen","email":"meesvandongen@gmail.com"},"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"_npmVersion":"8.19.2","description":"OAuth client for implicit and code flow with PKCE","directories":{},"_nodeVersion":"16.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","eslint":"^7.29.0","ts-jest":"^27.0.3","typedoc":"^0.21.2","prettier":"^2.3.2","typescript":"^4.3.4","@types/jest":"^26.0.23","microbundle":"^0.13.3","semantic-release":"^19.0.5","jsrsasign-reduced":"^8.0.15","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^9.0.1","typedoc-plugin-markdown":"^3.10.2","@semantic-release/github":"^8.0.6","@typescript-eslint/parser":"^4.28.1","@semantic-release/changelog":"^6.0.1","@typescript-eslint/eslint-plugin":"^4.28.1","@semantic-release/commit-analyzer":"^9.0.2","@semantic-release/release-notes-generator":"^10.0.3"},"_npmOperationalInternal":{"tmp":"tmp/oauth-client-core_1.4.0-next.6_1680254867345_0.7381344141374835","host":"s3://npm-registry-packages"}}},"time":{"created":"2021-08-03T14:23:29.253Z","modified":"2025-08-18T14:08:16.332Z","1.0.0-beta-1":"2021-08-03T14:23:29.535Z","1.0.0-beta-2":"2021-11-16T14:57:32.619Z","1.0.0-beta-2.1":"2021-11-19T13:52:25.413Z","1.0.0":"2022-05-24T10:18:51.568Z","1.0.1":"2022-06-03T08:26:38.782Z","1.0.2":"2022-06-03T12:05:23.698Z","1.0.3":"2022-07-22T14:31:57.911Z","1.0.3-beta.1":"2022-09-22T12:44:36.364Z","1.0.4":"2022-09-22T13:05:48.318Z","1.2.0":"2022-09-29T07:56:42.476Z","1.2.1":"2022-10-03T08:53:17.125Z","1.0.0-next.1":"2022-10-13T13:26:01.886Z","1.2.2-next.1":"2022-10-13T13:41:30.085Z","1.3.0-next.1":"2022-10-14T10:00:04.876Z","1.3.0":"2022-10-14T11:05:55.715Z","1.3.0-next.2":"2022-12-12T13:10:33.587Z","1.3.1-next.1":"2022-12-12T13:17:30.570Z","1.3.1-next.2":"2022-12-12T13:40:58.190Z","1.3.1":"2022-12-12T14:03:36.555Z","1.4.0-next.1":"2023-03-02T15:03:27.447Z","1.4.0-next.2":"2023-03-06T13:17:49.231Z","1.4.0-next.3":"2023-03-07T13:58:35.839Z","1.4.0-next.4":"2023-03-07T14:37:30.172Z","1.4.0-next.5":"2023-03-27T13:39:06.731Z","1.4.0-next.6":"2023-03-31T09:27:47.612Z"},"bugs":{"url":"https://github.com/Q24/oauth-client-core/issues"},"author":{"name":"ilionx"},"license":"GPL-3.0-or-later","homepage":"https://github.com/Q24/oauth-client-core","keywords":["oidc","openid","oauth","oauth2","implicit","code flow"],"repository":{"url":"git+https://github.com/Q24/oauth-client-core.git","type":"git"},"description":"OAuth client for implicit and code flow with PKCE","maintainers":[{"email":"rsanders@ilionx.com","name":"raymond.sanders"},{"email":"meesvandongen@gmail.com","name":"meesvandongen"},{"email":"kevin@xclusivemultimedia.com","name":"kevin.riemens"},{"email":"cedric.houben@live.nl","name":"cedric.houben"}],"readme":"","readmeFilename":""}