aerogear / keycloak-connect-graphql

Add Keyloak Authentication and Authorization to your GraphQL server.
Apache License 2.0
155 stars 22 forks source link

Undefined access token #103

Closed abelncm closed 4 years ago

abelncm commented 4 years ago

After calling const keycloak = new Keycloak( { ... } ) with my configurations (i believe secret should be inside credentials - credentials: {secret: ....} )

In my case my ApolloServer is like this

const apolloServer = new ApolloServer({ async context({ connection, req }) { const context = { ...contextFromOptions };

    // For a GraphQL subscription WebSocket request, there is no `req`
    if (connection) return context;

  await createDataLoaders(context);

  context.kauth = new KeycloakContext({ req });

  console.log(context.kauth.accessToken);

  return context;
},
debug: options.debug || false,
formatError: getErrorFormatter(),
schema,
subscriptions,
introspection: config.GRAPHQL_INTROSPECTION_ENABLED,
playground: config.GRAPHQL_PLAYGROUND_ENABLED

});

I put a ocnsole log to view accessToken in context but it's returning undefined. how can i debug and know that request is being made and view the keycloak response?

wtrocki commented 4 years ago

The access token will not be available in the context method - it will be available in resolvers like demoed in the example applications. The context method is used to create object and it is not used to authenticate or have info about user.

It might happen that request is missing access token. This usually happens when we forget to add keycloak middleware/protect graphql route.

Also it is worth to include version of the keycloak adapter that is being used. Some issues with old adapters might happen (we also did not tested version 11 of the adapter)

wtrocki commented 4 years ago

Added more info

abelncm commented 4 years ago

Ok, but i'm still getting not authenticated (i used @Auth so it's working) but I am authenticated though! How can i debug this?

wtrocki commented 4 years ago

This is probably due to misconfigured keycloak.

The way I would start is to try to run sample app (or README.md example). Understand what is happening there and inspect keycloak realm (server and client side clients created in keycloak.

If you succeed with that you can move the same patterns to your app.

abelncm commented 4 years ago

What version of keycloak adapter do you recommend? I'm using the latest

I don't think the configuration is wrong, because i can authenticate with the normal keycloak-connect procedure, but that only works for blocking endpoints.

Also do you use session? var memoryStore = new session.MemoryStore(); _keycloak = new Keycloak({ store: memoryStore }, keycloakConfig);

abelncm commented 4 years ago

i am using a bearer only client with a secret. i put a console log in your directiveResolvers i can see the authorization header (header.authorization) from context.kauth coming in ok.

Why would i have accessToken undefined in context.kauth if it is in context.kauth.headers.authorization?

wtrocki commented 4 years ago

Why you need those values in context?

abelncm commented 4 years ago

i'm just inspecting the context.kauth to better understand why it's not authenticated even with the token there

wtrocki commented 4 years ago

Is your client supplying token and it is logged into keycloak using the same realm? Please check our readme and sample app.

We are not able to help you with your problem unless we will be able to replicate it using example app. if you have any use case that is not documented please fork repo and modify example app so I can assist.

We are not able to assist with problem with your own codebase unless it is something that has steps to replicate on our side.

wtrocki commented 4 years ago

I will check sample app with the latest keycloak library..It might be that they broke something

abelncm commented 4 years ago

You can reproduce using this index.js

const express = require("express");

const { ApolloServer, gql } = require('apollo-server-express');

const Keycloak = require('keycloak-connect');

const { KeycloakContext, KeycloakTypeDefs, KeycloakSchemaDirectives } = require('keycloak-connect-graphql');

// A schema is a collection of type definitions (hence "typeDefs")
// that together define the "shape" of queries that are executed against
// your data.
const typeDefs = gql`
  # Comments in GraphQL strings (such as this one) start with the hash (#) symbol.

  # This "Book" type defines the queryable fields for every book in our data source.
  type Book {
    title: String
    author: String
  }

  # The "Query" type is special: it lists all of the available queries that
  # clients can execute, along with the return type for each. In this
  # case, the "books" query returns an array of zero or more Books (defined above).
  type Query {
    books: [Book] @auth
  }
`;

const books = [
  {
    title: 'Harry Potter and the Chamber of Secrets',
    author: 'J.K. Rowling',
  },
  {
    title: 'Jurassic Park',
    author: 'Michael Crichton',
  },
];

// Resolvers define the technique for fetching the types defined in the
// schema. This resolver retrieves books from the "books" array above.
const resolvers = {
  Query: {
    books: () => books,
  },
};

const app = express();
const keycloak = new Keycloak('keycloak.json');

app.use('/graphql', keycloak.middleware());

const server = new ApolloServer({
  typeDefs: [KeycloakTypeDefs, typeDefs], // 1. Add the Keycloak Type Defs
  schemaDirectives: KeycloakSchemaDirectives, // 2. Add the KeycloakSchemaDirectives
  resolvers,
  context: ({ req }) => {
    return {
      kauth: new KeycloakContext({ req }) // 3. add the KeycloakContext to `kauth`
    }
  }
});

server.applyMiddleware({ app });

app.listen(4000, () =>
  console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);

and in your keycloak.json file with the following structure (for bearer-only client):

{
  "realm": "yourrealm",
  "bearer-only": true,
  "auth-server-url": "http://yourkeycloakip/auth",
  "ssl-required": "none",
  "resource": "your-resource",
  "verify-token-audience": true,
  "use-resource-role-mappings": true,
  "confidential-port": 0,
  "credentials": {
    "secret": "your-keycloak-client-secret"
  }
}

in your playground http://localhost:4000/graphql use this to query the book (note that i used @auth in query books)

{
  books {
    title
  }
}

it should give "User not Authenticated"

wtrocki commented 4 years ago

You are missing middleware/protect method from keycloak. Without that context will not have Auth data. please check readme or sample apps.

wtrocki commented 4 years ago

See https://github.com/aerogear/keycloak-connect-graphql/blob/master/examples/lib/common.js

abelncm commented 4 years ago

Hi, I applied the following modifications, but still I'm getting "User not authenticated". (I removed credentials.secret in keycloak.json since it's in the code)

const fs = require('fs');
const path = require('path');
const express = require("express");
const session = require('express-session');

const { ApolloServer, gql } = require('apollo-server-express');

const Keycloak = require('keycloak-connect');

const { KeycloakContext, KeycloakTypeDefs, KeycloakSchemaDirectives } = require('keycloak-connect-graphql');

// A schema is a collection of type definitions (hence "typeDefs")
// that together define the "shape" of queries that are executed against
// your data.
const typeDefs = gql`
  # Comments in GraphQL strings (such as this one) start with the hash (#) symbol.

  # This "Book" type defines the queryable fields for every book in our data source.
  type Book {
    title: String
    author: String
  }

  # The "Query" type is special: it lists all of the available queries that
  # clients can execute, along with the return type for each. In this
  # case, the "books" query returns an array of zero or more Books (defined above).
  type Query {
    books: [Book] @auth
  }
`;

const books = [
  {
    title: 'Harry Potter and the Chamber of Secrets',
    author: 'J.K. Rowling',
  },
  {
    title: 'Jurassic Park',
    author: 'Michael Crichton',
  },
];

// Resolvers define the technique for fetching the types defined in the
// schema. This resolver retrieves books from the "books" array above.
const resolvers = {
  Query: {
    books: () => books,
  },
};

const app = express();

const keycloakConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'keycloak.json')));
const memoryStore = new session.MemoryStore();

  app.use(session({
    secret: process.env.SESSION_SECRET_STRING || 'my-keycloak-secret-added-here',
    resave: false,
    saveUninitialized: true,
    store: memoryStore
  }));

const keycloak = new Keycloak({store: memoryStore}, keycloakConfig);

// Install general keycloak middleware
app.use(keycloak.middleware({admin: '/graphql'  }));
app.use('/graphql', keycloak.middleware());

const server = new ApolloServer({
  typeDefs: [KeycloakTypeDefs, typeDefs], // 1. Add the Keycloak Type Defs
  schemaDirectives: KeycloakSchemaDirectives, // 2. Add the KeycloakSchemaDirectives
  resolvers,
  context: ({ req }) => {
    return {
      kauth: new KeycloakContext({ req }) // 3. add the KeycloakContext to `kauth`
    }
  }
});

server.applyMiddleware({ app });

app.listen(4000, () =>
  console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);
davidschrooten commented 4 years ago

Having similar problem, after keycloak in kubernetes crashed and restoring a backup in a newer keycloak I suddenly am having same problem. Middleware are all properly defined.

abelncm commented 4 years ago

Actually it's all working good. Only thing is you have to use a public client from keycloak and all is good.

wtrocki commented 4 years ago

@davidq2q Let's create separate issue

proftom commented 2 years ago

Actually it's all working good. Only thing is you have to use a public client from keycloak and all is good.

what do you mean "use a public client from keycloak"?

proftom commented 2 years ago

The access token will not be available in the context method - it will be available in resolvers like demoed in the example applications. The context method is used to create object and it is not used to authenticate or have info about user.

It might happen that request is missing access token. This usually happens when we forget to add keycloak middleware/protect graphql route.

@wtrocki are you saying that context.kauth.accessToken should not contain the Bearer access token? What is the point of context.kauth.accessToken, when and how sets it?

wtrocki commented 2 years ago

what do you mean "use a public client from keycloak"?

Keycloak have two client types. Bearer and Public. Bearer client is used only for backends to check token - it doesn't provide login logic/session etc.

are you saying that context.kauth.accessToken should not contain the Bearer access token?

That would be part of the keycloak node.js library logic. All we do here is export this logic to graphql layer.