npm package: @lgibbs/keycloak-auth
Description: "Framework-agnostic Javascript Keycloak auth client"
Author: lgibbs
Latest version: 2.0.0

# keycloak-auth

Framework-agnostic JavaScript Keycloak authentication client.

## Installation

```bash
npm install @lgibbs/keycloak-auth
```

## Usage

### Plain JavaScript (No Framework)

```html
<!DOCTYPE html>
<html>
<head>
  <script type="module" src="/path/to/keycloak-auth.js"></script>
</head>
<body>
  <div id="app">Loading…</div>
  <script type="module">
    import { initAuth, subscribe, login } from '/path/to/keycloak-auth.js';

    await initAuth({
      url: 'https://kc.example.com/auth',
      realm: 'myrealm',
      clientId: 'my-client',
      onLoad: 'login-required',
      silentCheckUri: `${window.origin}/silent-check.html`
    });

    subscribe(({ authenticated, token }) => {
      const el = document.getElementById('app');
      if (authenticated) {
        el.textContent = `Logged in! Token: ${token.slice(0,10)}…`;
      } else {
        el.innerHTML = `<button id="login">Log in</button>`;
        document.getElementById('login').onclick = login;
      }
    });
  </script>
</body>
</html>
```

### React

```jsx
import React, { useEffect, useState } from 'react';
import { initAuth, subscribe, login, logout } from 'keycloak-auth';

function App() {
  const [auth, setAuth] = useState({ authenticated: false });

  useEffect(() => {
    initAuth({
      url: process.env.REACT_APP_KC_URL,
      realm: process.env.REACT_APP_KC_REALM,
      clientId: process.env.REACT_APP_KC_CLIENT_ID,
      onLoad: 'check-sso',
      silentCheckUri: `${window.location.origin}/silent-check.html`
    });
    const unsub = subscribe(setAuth);
    return unsub;
  }, []);

  if (!auth.authenticated) {
    return <button onClick={login}>Log in</button>;
  }
  return (
    <div>
      <p>Welcome! Token: {auth.token.substring(0, 10)}…</p>
      <button onClick={logout}>Log out</button>
    </div>
  );
}
```

### Vue 3

```js
import { createApp } from 'vue';
import App from './App.vue';
import { initAuth, subscribe } from 'keycloak-auth';

initAuth({
  url: import.meta.env.VITE_KC_URL,
  realm: import.meta.env.VITE_KC_REALM,
  clientId: import.meta.env.VITE_KC_CLIENT_ID,
  onLoad: 'login-required'
});

const app = createApp(App);
app.provide('kcSubscribe', subscribe);
app.mount('#app');
```

```vue
<template>
  <div v-if="auth.authenticated">
    <p>Welcome—token: {{ auth.token.substr(0,10) }}…</p>
    <button @click="logout">Logout</button>
  </div>
  <div v-else>
    <button @click="login">Login</button>
  </div>
</template>

<script>
import { inject, onMounted, ref } from 'vue';
import { login, logout } from 'keycloak-auth';

export default {
  setup() {
    const auth = ref({});
    const kcSubscribe = inject('kcSubscribe');
    onMounted(() => kcSubscribe(a => (auth.value = a)));
    return { auth, login, logout };
  }
};
</script>
```

### Angular

```ts
import { Injectable } from '@angular/core';
import * as kcAuth from 'keycloak-auth';

@Injectable({ providedIn: 'root' })
export class KeycloakService {
  init() {
    return kcAuth.initAuth({
      url: environment.kcUrl,
      realm: environment.kcRealm,
      clientId: environment.kcClientId
    });
  }
  login() { kcAuth.login(); }
  logout() { kcAuth.logout(); }
  getToken() { return kcAuth.getToken(); }
  subscribe(cb: any) { return kcAuth.subscribe(cb); }
}
```

```ts
import { Component, OnInit } from '@angular/core';
import { KeycloakService } from './keycloak.service';

@Component({
  selector: 'app-root',
  template: `
    <ng-container *ngIf="auth.authenticated; else loginTpl">
      <p>Logged in! Token: {{ auth.token | slice:0:10 }}…</p>
      <button (click)="logout()">Logout</button>
    </ng-container>
    <ng-template #loginTpl>
      <button (click)="login()">Login</button>
    </ng-template>
  `
})
export class AppComponent implements OnInit {
  auth = { authenticated: false, token: null };

  constructor(private kc: KeycloakService) {}

  async ngOnInit() {
    await this.kc.init();
    this.kc.subscribe(a => (this.auth = a));
  }

  login() { this.kc.login(); }
  logout() { this.kc.logout(); }
}
```

## API Reference

* **initAuth(config)**: Initializes Keycloak with `{ url, realm, clientId, onLoad?, silentCheckUri? }`.
* **login()**: Redirects the browser to Keycloak’s login page.
* **logout()**: Logs out and clears the auth state.
* **getToken()**: Returns the current token or `null`.
* **subscribe(callback)**: Subscribes to auth state changes (`{ authenticated, token, tokenParsed }`). Returns an unsubscribe function.

## Development & Build

### Optional Rollup Build

To generate UMD & ESM bundles:

1. Install dev dependencies:

   ```bash
   npm install --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs rollup-plugin-terser
   ```
2. Create `rollup.config.js`:

   ```js
   import resolve from '@rollup/plugin-node-resolve';
   import commonjs from '@rollup/plugin-commonjs';
   import { terser } from 'rollup-plugin-terser';

   export default {
     input: 'keycloak-auth.js',
     output: [
       { file: 'dist/keycloak-auth.esm.js', format: 'esm' },
       { file: 'dist/keycloak-auth.umd.js', format: 'umd', name: 'KCAuth' }
     ],
     plugins: [resolve(), commonjs(), terser()]
   };
   ```
3. Update `package.json`:

   ```json
   "scripts": {
     "build": "rollup -c"
   },
   "main": "dist/keycloak-auth.umd.js",
   "module": "dist/keycloak-auth.esm.js",
   "unpkg": "dist/keycloak-auth.umd.js"
   ```
4. Run:

   ```bash
   npm run build
   ```

## Publishing to npm

1. **Login**:

   ```bash
   npm login
   ```
2. **Publish**:

   ```bash
   npm publish --access public
   ```
3. **Versioning**: bump version in `package.json`, tag in git (`git tag v1.0.1 && git push --tags`), then re-publish.

## License

MIT