Skip to content

Instantly share code, notes, and snippets.

@PascalSenn
Last active May 15, 2025 11:30
Show Gist options
  • Select an option

  • Save PascalSenn/43fedfbc1bc96692d99263a9da2d9ac4 to your computer and use it in GitHub Desktop.

Select an option

Save PascalSenn/43fedfbc1bc96692d99263a9da2d9ac4 to your computer and use it in GitHub Desktop.

Revisions

  1. PascalSenn revised this gist Mar 13, 2020. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions auth-apollo-how-to.md
    Original file line number Diff line number Diff line change
    @@ -90,7 +90,13 @@ Keep in mind though:
    As an alternative you could use a query string to authenticate :slightly_smiling_face:
    You can have a look at the whole Authentication configuration here:
    https://gist.github.com/PascalSenn/a3204e7bb880705a178048a43ee2147c
    Interceptor
    https://gist.github.com/PascalSenn/6e2a1838e44e836017ede3ce55b4fc3b
    And lastly all of the stuff I've written archived under:
    https://gist.github.com/PascalSenn/43fedfbc1bc96692d99263a9da2d9ac4
  2. PascalSenn revised this gist Mar 12, 2020. 1 changed file with 21 additions and 15 deletions.
    36 changes: 21 additions & 15 deletions auth-apollo-how-to.md
    Original file line number Diff line number Diff line change
    @@ -3,55 +3,60 @@ After that the authentication is cached and will always yield the same result.

    A web socket connection starts over HTTP. A HTTP request with the Upgrade header is send to the back end.
    This request is then "upgraded" into a web socket that runs over the web socket pipeline.
    The main issue with web sockets is that you cannot set additional headers to this initial HTTP header.
    The main issue with web sockets is that you cannot set additional headers with this initial HTTP header.
    Therefore the authentication will fail and the request will be unauthenticated.
    The HTTP context of the initial request will last as long as the web socket connection is running.

    The trick we do is that we add a stub authentication scheme:
    ```csharp
    AddJwtBearer("Websockets", ctx => { })
    ```
    We need to authenticate the request against this scheme. This way we can then later authenticate against our default scheme.
    We can do this when we use the ForwardDefaultSelector on our default scheme to forward to the stub ("Websockets") scheme.
    We can do this when we use the `ForwardDefaultSelector` on our default scheme to forward to the stub ("Websockets") scheme.

    We can use this code snipped:
    ```csharp
     if (!context.Items.ContainsKey(AuthenticationSocketInterceptor.HTTP_CONTEXT_WEBSOCKET_AUTH_KEY) && // <- do we need later
    context.Request.Headers.TryGetValue("Upgrade", out var value) && <-- checking if it is a Upgrade request
    context.Request.Headers.TryGetValue("Upgrade"out var value&& // <-- checking if it is a Upgrade request
            value.Count > 0 && 
    value[0is string stringValue && 
    stringValue == "websocket")
     {
      return "Websockets";
     }
     return "YourDefaultScheme";

    ```
    Now the request is Unauthenticated and the web socket is going to be established. Sweet.
    We need to intercept the connection request. We can do this by specifying a SocketConnectionInterceptor.
    services.AddSingleton<ISocketConnectionInterceptor<HttpContext>, AuthenticationSocketInterceptor>();
    We need to intercept the connection request. We can do this by specifying a `SocketConnectionInterceptor`.
    `services.AddSingleton<ISocketConnectionInterceptor<HttpContext>, AuthenticationSocketInterceptor>();`

    The socket interceptor basically does the same thing as the authentication middle ware of .net core itself.
    Parts of the code can actually be the same. I made a gist for it because I do believe that this would make the message a bit too long :smile:
    The idea is as follows.
    We try to get the key of the connection_init request of apollo. WEBOCKET_PAYLOAD_AUTH_KEY
    We try to get the key of the connection_init request of apollo. `WEBOCKET_PAYLOAD_AUTH_KEY`
    This is the key you use here
    ```js
    const wsClient = new SubscriptionClient(`ws://localhost:5000/`, {
    reconnect: true,
    connectionParams: {
    authToken: user.authToken, //WEBOCKET_PAYLOAD_AUTH_KEY = "authToken
    },"
    });
    We take this key out of the properties in the back end and store it in on the HTTP Context: HTTP_CONTEXT_WEBSOCKET_AUTH_KEY
    ```
    We take this key out of the properties in the back end and store it in on the HTTP Context: `HTTP_CONTEXT_WEBSOCKET_AUTH_KEY`
    We then authenticate again, but this time against the default schema.
    You can see all the details in the gist below
    https://gist.github.com/PascalSenn/6e2a1838e44e836017ede3ce55b4fc3b
    And now comes the third an last part.
    We first forwarded the Upgrade request from the default scheme on to a stub scheme,
    then intercepted the connection_init message and authenticated again but this time against the default scheme.
    We first forwarded the Upgrade request from the default scheme on to a stub scheme, then intercepted the connection_init message and authenticated again but this time against the default scheme.
    Now the default scheme has only have to understand that it should take the token out of the HTTPContext rather than out of the Headers.
    So lets have a look again at the forwarder first. The first time we skipped the default scheme because it hat the Upgrade header
    The HTTP Context is still the same, so it is still a Upgrade request. But now the context contains the token.
    First line:
     if (!context.Items.ContainsKey(AuthenticationSocketInterceptor.HTTP_CONTEXT_WEBSOCKET_AUTH_KEY) && // <- now we nned it :
    ```csharp
     if (!context.Items.ContainsKey(AuthenticationSocketInterceptor.HTTP_CONTEXT_WEBSOCKET_AUTH_KEY) && // <- now we need it :)
    context.Request.Headers.TryGetValue("Upgrade", out var value) &&
            value.Count > 0 && 
    value[0] is string stringValue && 
    @@ -60,9 +65,10 @@ First line:
      return "Websockets";
     }
     return "YourDefaultScheme";

    ```
    Sweet. The default scheme does not know where it finds the token though.
    There for we need to tell the scheme how to get the token with the TokenRetriever
    There for we need to tell the scheme how to get the token with the `TokenRetriever`
    ```csharp
    options.TokenRetriever = new Func<HttpRequest, string>(req =>
    {
        if (req.HttpContext.Items.TryGetValue(
    @@ -76,10 +82,10 @@ options.TokenRetriever = new Func<HttpRequest, string>(req =>
             var fromQuery = TokenRetrieval.FromQueryString();  
              return fromHeader(req) ?? fromQuery(req);
         });

    ```
    Now you are all set :smile: eaaasy :smile:
    Keep in mind though:
    The token does not expire or is refreshed, the web socket could be open for days. You have to close the web socket from the back end for security reasons i guess
    **The token does not expire or is refreshed, the web socket could be open for days. You have to close the web socket from the back end for security reasons i guess**
    As an alternative you could use a query string to authenticate :slightly_smiling_face:
  3. PascalSenn created this gist Mar 12, 2020.
    90 changes: 90 additions & 0 deletions auth-apollo-how-to.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,90 @@
    The problem with authentication of web sockets is that you can only authenticate a http request once against a authentication scheme.
    After that the authentication is cached and will always yield the same result.

    A web socket connection starts over HTTP. A HTTP request with the Upgrade header is send to the back end.
    This request is then "upgraded" into a web socket that runs over the web socket pipeline.
    The main issue with web sockets is that you cannot set additional headers to this initial HTTP header.
    Therefore the authentication will fail and the request will be unauthenticated.
    The HTTP context of the initial request will last as long as the web socket connection is running.

    The trick we do is that we add a stub authentication scheme:
    AddJwtBearer("Websockets", ctx => { })
    We need to authenticate the request against this scheme. This way we can then later authenticate against our default scheme.
    We can do this when we use the ForwardDefaultSelector on our default scheme to forward to the stub ("Websockets") scheme.

    We can use this code snipped:
     if (!context.Items.ContainsKey(AuthenticationSocketInterceptor.HTTP_CONTEXT_WEBSOCKET_AUTH_KEY) && // <- do we need later
    context.Request.Headers.TryGetValue("Upgrade", out var value) && <-- checking if it is a Upgrade request
            value.Count > 0 && 
    value[0] is string stringValue && 
    stringValue == "websocket")
     {
      return "Websockets";
     }
     return "YourDefaultScheme";

    Now the request is Unauthenticated and the web socket is going to be established. Sweet.
    We need to intercept the connection request. We can do this by specifying a SocketConnectionInterceptor.
    services.AddSingleton<ISocketConnectionInterceptor<HttpContext>, AuthenticationSocketInterceptor>();

    The socket interceptor basically does the same thing as the authentication middle ware of .net core itself.
    Parts of the code can actually be the same. I made a gist for it because I do believe that this would make the message a bit too long :smile:
    The idea is as follows.
    We try to get the key of the connection_init request of apollo. WEBOCKET_PAYLOAD_AUTH_KEY
    This is the key you use here
    const wsClient = new SubscriptionClient(`ws://localhost:5000/`, {
    reconnect: true,
    connectionParams: {
    authToken: user.authToken, //WEBOCKET_PAYLOAD_AUTH_KEY = "authToken
    },"
    });
    We take this key out of the properties in the back end and store it in on the HTTP Context: HTTP_CONTEXT_WEBSOCKET_AUTH_KEY
    We then authenticate again, but this time against the default schema.
    You can see all the details in the gist below
    https://gist.github.com/PascalSenn/6e2a1838e44e836017ede3ce55b4fc3b

    And now comes the third an last part.
    We first forwarded the Upgrade request from the default scheme on to a stub scheme,
    then intercepted the connection_init message and authenticated again but this time against the default scheme.
    Now the default scheme has only have to understand that it should take the token out of the HTTPContext rather than out of the Headers.

    So lets have a look again at the forwarder first. The first time we skipped the default scheme because it hat the Upgrade header
    The HTTP Context is still the same, so it is still a Upgrade request. But now the context contains the token.
    First line:
     if (!context.Items.ContainsKey(AuthenticationSocketInterceptor.HTTP_CONTEXT_WEBSOCKET_AUTH_KEY) && // <- now we nned it :
    context.Request.Headers.TryGetValue("Upgrade", out var value) &&
            value.Count > 0 && 
    value[0] is string stringValue && 
    stringValue == "websocket")
     {
      return "Websockets";
     }
     return "YourDefaultScheme";

    Sweet. The default scheme does not know where it finds the token though.
    There for we need to tell the scheme how to get the token with the TokenRetriever
    options.TokenRetriever = new Func<HttpRequest, string>(req =>
    {
        if (req.HttpContext.Items.TryGetValue(
               AuthenticationSocketInterceptor.HTTP_CONTEXT_WEBSOCKET_AUTH_KEY,
                  out object token) &&
     token is string stringToken)
           {
               return stringToken;
             }
             var fromHeader = TokenRetrieval.FromAuthorizationHeader();
             var fromQuery = TokenRetrieval.FromQueryString();  
              return fromHeader(req) ?? fromQuery(req);
         });

    Now you are all set :smile: eaaasy :smile:
    Keep in mind though:
    The token does not expire or is refreshed, the web socket could be open for days. You have to close the web socket from the back end for security reasons i guess

    As an alternative you could use a query string to authenticate :slightly_smiling_face:

    You can have a look at the whole Authentication configuration here:
    https://gist.github.com/PascalSenn/a3204e7bb880705a178048a43ee2147c
    Interceptor
    https://gist.github.com/PascalSenn/6e2a1838e44e836017ede3ce55b4fc3b
    And lastly all of the stuff I've written archived under: