# keycloak-connect-graphql-multirealm-v4

> Add Keycloak authentication and authorization to your GraphQL server.

Latest version **2.1.0** (published 2024-04-16) · Apache-2.0 license · 0 weekly downloads

## Install

```sh
npm install keycloak-connect-graphql-multirealm-v4
pnpm add keycloak-connect-graphql-multirealm-v4
yarn add keycloak-connect-graphql-multirealm-v4
bun add keycloak-connect-graphql-multirealm-v4
```

## Health

**Score 25/100 (F)** — status: abandoned.

Positive: has types; no vulnerabilities; high quality score.

Warnings: low downloads; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 2.1.0 |
| Published | 2024-04-16 |
| First published | 2023-07-11 |
| Weekly downloads | 0 |
| License | Apache-2.0 |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 7 |
| Unpacked size | 108.7 KB |
| Known vulnerabilities | 0 (+3 in 2 direct dependencies) |
| Install scripts | no |
| GitHub stars | 1 |
| Author | Dinuth De Zoysa |
| Maintainers | nmursith |
| Keywords | graphql, apollo, keycloak, authentication, express |

## Links

- npm: https://www.npmjs.com/package/keycloak-connect-graphql-multirealm-v4
- Repository: https://github.com/ddezoysa/keycloak-connect-graphql-multirealm
- Homepage: https://iamdinuth.business.site
- Issues: https://github.com/ddezoysa/keycloak-connect-graphql-multirealm/issues
- npm.io page: https://npm.io/package/keycloak-connect-graphql-multirealm-v4

## Dependencies (7)

- [node-cache](https://npm.io/package/node-cache.md) ^5.1.2
- [graphql-tag](https://npm.io/package/graphql-tag.md) ^2.12.6
- [jsonwebtoken](https://npm.io/package/jsonwebtoken.md) ^9.0.2
- [@apollo/server](https://npm.io/package/@apollo/server.md) ^4.10.2
- [@apollo/subgraph](https://npm.io/package/@apollo/subgraph.md) ^2.7.2
- [@graphql-tools/utils](https://npm.io/package/@graphql-tools/utils.md) ^10.1.2
- [composable-middleware](https://npm.io/package/composable-middleware.md) ^0.3.1

## Alternatives

- [apollo-link-http-common](https://npm.io/package/apollo-link-http-common.md) — 879.0K weekly downloads
- [react-relay](https://npm.io/package/react-relay.md) — 336.8K weekly downloads
- [relay-test-utils](https://npm.io/package/relay-test-utils.md) — 181.6K weekly downloads
- [@vendure/core](https://npm.io/package/@vendure/core.md) — 14.8K weekly downloads
- [@pnpm/deps.graph-sequencer](https://npm.io/package/@pnpm/deps.graph-sequencer.md) — 13.4K weekly downloads

## Recent versions

- 2.1.0 (latest) — 2024-04-16
- 0.0.10 — 2023-07-11

## README

# keycloak-connect-graphql-multirealm

A comprehensive solution for adding keycloak Authentication and Authorization to your Express based GraphQL server in an multi-realm setup to implement multi tenant applications.

This Keycloak GraphQL Multi-Realm adapter was created based on,
- keycloak-connect-graphql: https://github.com/aerogear/keycloak-connect-graphql
- keycloak-connect-multirealm: https://github.com/devsu/keycloak-nodejs-multirealm

Please refer those repositories for more documentation and usage aspects.

# Usage
## Installation

Install library 
```bash
npm install keycloak-connect keycloak-connect-graphql-multirealm
```

Install required dependencies:
```bash
npm install --save  graphql keycloak-connect apollo-server-express 
```

## Keycloak Multi-Realm Configuration

```javascript
  app.use(
    session({
      secret: sessionSecret,
      resave: false,
      saveUninitialized: true,
      store: memoryStore,
    })
  );

  const keycloak = new KeycloakMultiRealm(
    {
      store: memoryStore,
      authKey: "my-auth-key-for-static-key-auth",
    },
    keycloakClientConfig as any
  );

  app.use(
    keycloak.middleware({
      admin: graphqlPath,
    })
  );

  app.use(graphqlPath, keycloak.middleware());
```

`KeycloakMultiRealm` can have optional functions: 
- `realmTenantMappingFunction?: ((tenantKey: string) => string) | undefined`
  
  This function can be used to convert tenantKey to realmName if the tenantKey is not equals to realmName.

```javacript
export function realmTenantMappingFunction(tenantKey: string) {
  if (!tenantKey) return undefined;
  else return `${tenantKey.toUpperCase()}-REALM`;
}
```

- `clientSecretResolverFunction?: ((realmName: string, clientId: string) => string) | undefined)`

  If @hasPermission Directive is used, you need to use confidential client in your `keycloakClientConfig`. But `ClientSecret` of each application would be different in each Realm. You can pass an Function to resolve `ClientSecret` based on the `realmName`.

```javascript
export const clientSecretResolverFunction = (realmName: string, clientId: string) => {
  const adminAccessToken = getToken(keycloakAdminConfig.realmName, keycloakAdminConfig.username, keycloakAdminConfig.password).access_token;
  const clientsResponse = request('GET', `${keycloakAdminConfig.baseUrl}/admin/realms/${realmName}/clients`, {
    qs: { clientId },
    headers: {
      Authorization: `Bearer ${adminAccessToken}`,
    },
  });
  const client = JSON.parse(clientsResponse.getBody('utf8'))[0];
  const secretResponse = request('GET', `${keycloakAdminConfig.baseUrl}/admin/realms/${realmName}/clients/${client.id}/client-secret`, {
    headers: {
      Authorization: `Bearer ${adminAccessToken}`,
    },
  });

  return JSON.parse(secretResponse.getBody('utf8')).value;
}
```
## Adding GraphQL Directives to Schema

Directive Transformers are available in `example/KeycloakDirectiveTransformers.ts`. 

```javascript
export function getSchema() {
  const federatedSchema = buildSubgraphSchema([{ typeDefs: mergeTypeDefs([typeDefs, gql(KeycloakTypeDefs)]) }]);

  let schema = addResolversToSchema({
    schema: federatedSchema,
    resolvers: mergeResolvers([resolvers]),
    inheritResolversFromInterfaces: true,
  });

  schema = authDirectiveTransformer(schema, 'auth');
  schema = authKeyDirectiveTransformer(schema, 'authKey');
  schema = tenantDirectiveTransformer(schema, 'tenant');
  schema = hasRoleDirectiveTransformer(schema, 'hasRole');
  schema = hasPermissionDirectiveTransformer(schema, 'hasPermission');

  return schema;
}
```
## Apollo Server Configuration

```javascript
  const server = new ApolloServer({
    schema: getSchema(),
    formatError: (err) => {
      if (!errorHandling.stacktrace) delete err.extensions.exception.stacktrace;
      return err;
    },
    context: async ({ req }) => {
      let tenantKey;
      if (req.headers['x-tenant-key']) {
        tenantKey = String(req.headers['x-tenant-key']);
      } else {
        tenantKey = req.originalUrl.replace(graphqlPath, ``).substring(1);
      }
      if (tenantKey == '') tenantKey = 'master';
      return {
        //@ts-ignore
        kauth: new MultiRealmKeycloakContext({ req }, keycloak, keycloakResourceServer),
        models: await getDatabaseModel(tenantKey),
        masterModels: database,
        tenantKey: tenantKey,
      };
    },
  });

  await server.start();
  server.applyMiddleware({
    app,
    path: graphqlPath,
  });
```

## Apollo Gateway Configuration

```javascript
class RequestDataSource extends RemoteGraphQLDataSource {
  willSendRequest = async ({ request, context }) => {
    if (context.tenantKey) {
      request.http?.headers.set("x-tenant-key", context.tenantKey);
    }
    if (context.req?.headers["authorization"]) {
      request.http?.headers.set("Authorization", context.req?.headers["authorization"]);
    }
  };
}

const gateway = new ApolloGateway({
  pollIntervalInMs: 10000,
  supergraphSdl: new IntrospectAndCompose({
    subgraphs: [], // provide serviceList [{name, url}]
  }),
  debug: true,
  buildService: ({ url }) => new RequestDataSource({ url }),
});
const server = new ApolloServer({
  gateway,
  context: ({ req }) => {
    let tenantKey;
    if (req.headers["x-tenant-key"]) {
      tenantKey = String(req.headers["x-tenant-key"]);
    } else {
      tenantKey = req.originalUrl.replace(graphqlPath, ``).substring(1);
    }
    return {
      req: req,
      tenantKey: tenantKey != "" ? tenantKey : "master",
    };
  },
  plugins: [ApolloServerPluginInlineTraceDisabled()],
});
await server.start();
server.applyMiddleware({
  app,
  path: `${graphqlPath}`,
});
```

---
_Source: https://npm.io/package/keycloak-connect-graphql-multirealm-v4 · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
