graphql-weaver icon indicating copy to clipboard operation
graphql-weaver copied to clipboard

Authenticating User at API Gateway and then redirecting them to microservices

Open shreyansh-zazz opened this issue 5 years ago • 7 comments

I am trying to build an API Gateway with GraphQL for my microservices architecture. Here's how I am trying to implement this:

  • Suppose we have 4 microservices named as M1, M2, M3, and M4
  • The M1 microservice is the one which is responsible for logging in the user and generating JWT token
  • Whenever user makes a request for other microservices like M2, M3, or M4, he must be logged in to the system

Following are the expected API Gateway features:

  • Wherever a request comes, if it's for microservice M1 the API Gateway should just redirect the user to that gateway
  • If user requests for M2, M3 or M4 microservices the API Gateway should check for JWT token, decode it, and append the data into the header.

Following are the things is not expected from the API Gateway:

  • Writing all schemas from all microservices in the API Gateway. Since it is not practical, if I change the schema in Microservice M3 I have to change it at the API gateway also which I don't want to do

This framework is all cool, but I don't find any examples or methods through which I can achieve this. Maybe someone can guide me through.

Perhaps @Yogu you can help me.

Thanks

shreyansh-zazz avatar Apr 01 '19 09:04 shreyansh-zazz

To write custom authentication logic, you need to do two things:

  • Make sure that you have access to the request data in the GraphQL context object. For example, if you're using express-graphql, the express request will be passed as context, so you're all set.
  • Implement a custom custom GraphQLClient client, e.g. by extending HttpGraphQLClient. For example, you could override getHeaders, look into the request (passed as context), do the token decoding and set appropiate headers for the proxied request.

Then, you can use a config like this:

const schema = weaveSchemas({
    endpoints: [, {
        namespace: 'M1',
        url: 'http://m1/...'
    }, {
        namespace: 'M2',
        client: new CustomHttpGraphQLClient('http://m2/...')
    }, /* ... */
    ]
});

I hope this helps

Yogu avatar Apr 01 '19 11:04 Yogu

@Yogu Few doubts:

  • What is the `CustomHttpGraphQLClient?
  • Where would I write the code for decoding the JWT token?
  • Do I have to pass the client property for microservices M3 and M4 also?

shreyansh-zazz avatar Apr 01 '19 11:04 shreyansh-zazz

CustomGraphQLClient would be your subclass of HttpGraphQLClient, something like this:


class CustomGraphQLClient extends HttpGraphQLClient {
    protected async getHeaders(document: DocumentNode, variables?: { [name: string]: any }, context?: any, introspect?: boolean): Promise<{ [index: string]: string }> {
        const request = context as Request;
        // implement your logic here for decoding the token based on the express request
        const regularHeaders = super.getHeaders(document, variables, context, introspect);

        return {
            ...regularHeaders,
            // add your headers here
        };
    }
}

Do I have to pass the client property for microservices M3 and M4 also?

I understood that M2, M3 and M4 are basically the same, so yes you would need to add endpoints with the custom client for all three of them.

Yogu avatar Apr 01 '19 19:04 Yogu

@Yogu I understand that above mentioned solution will add a header (in which token data will be available for other to use) to the original request but I still don't understand how it will stop the request at the API Gateway if no JWT Token is found int the request header?

shreyansh-zazz avatar Apr 01 '19 19:04 shreyansh-zazz

It's completely up to you how you implement that method, you can just throw an Error if the user is not authenticated.

Yogu avatar Apr 02 '19 00:04 Yogu

@Yogu Hey what if I am not using Typescript?

class CustomGraphQLClient extends HttpGraphQLClient {
    protected async getHeaders(document: DocumentNode, variables?: { [name: string]: any }, context?: any, introspect?: boolean): Promise<{ [index: string]: string }> {
        const request = context as Request;
        // implement your logic here for decoding the token based on the express request
        const regularHeaders = super.getHeaders(document, variables, context, introspect);

        return {
            ...regularHeaders,
            // add your headers here
        };
    }
}

Then how can I implement this custom class?

shreyansh-zazz avatar Apr 12 '19 06:04 shreyansh-zazz

Ah, just remove some parts:

class CustomGraphQLClient extends HttpGraphQLClient {
    async getHeaders(document, variables) {
        const request = context;
        // implement your logic here for decoding the token based on the express request
        const regularHeaders = super.getHeaders(document, variables, context, introspect);

        return {
            ...regularHeaders,
            // add your headers here
        };
    }
}

If you need to target an older version of JavaScript, just use a transpiler of your choice.

Yogu avatar Apr 13 '19 22:04 Yogu