Search code examples
c#jwtwsdlcredentials

Bearer authentication with System.Net.ICredentials


I have an application that compiles service reference assembly on-the-fly for given WSDL address:

  1. Use System.Net.WebClient to OpenRead the target service as Stream

  2. Describe the WSDL as Code Compile Unit: https://learn.microsoft.com/en-us/dotnet/api/system.web.services.description.servicedescriptionimporter

    ServiceDescription description = ServiceDescription.Read(stream);

  3. Compile the unit into Assembly: https://learn.microsoft.com/en-us/dotnet/api/system.codedom.compiler.codedomprovider.compileassemblyfromdom

All the services types in that assembly then have Credentials (type ICredentials) that can be set e.g.

someService.GetType().GetProperty("Credentials").SetValue(someService, new NetworkCredential(userName, password));

which works as expected in Basic Authentication scenario.

I now need to start supporting Bearer authentication (not sure if correct terminology) i.e. on the very request level of those services, have Authorization header with value Bearer <token>. <token> is jwt token obtained with OAuth2 protocol. The authentication itself works as I already use it to read the WSDL in step 1: client.Headers.Add("Authorization", "Bearer ...");

However, I'm not able to use OperationContextScope to inject headers (How to add HTTP Header to SOAP Client) to my service reference calls because my compiled types do not contain InnerChannel.

I think under-the-hood NetworkCredentials result in header like Authorization: Basic <credentials>. This is so similar to what I need (Authorization: Bearer <token>) that I can't believe there simply isn't ICredentials sub-class for this purpose. Is there? Do I have any other options? I'm almost desperate enough to write my own proxy that forwards all my calls with that authorization header.


Solution

  • I happened to find a solution myself.

    When the WSDL is imported into CodeCompileUnit with ServiceDescriptionImporter, all service types of the unit derive from System.Web.Services.Protocols.SoapHttpClientProtocol. This base class declaration can be replaced with a custom derivation of it, and thus override GetWebRequest and inject custom HTTP headers to all web requests of that service.

    foreach(CodeNamespace ns in unit1.Namespaces)
    {
      foreach(CodeTypeDeclaration t in ns.Types)
      {
        foreach(CodeTypeReference bt in t.BaseTypes)
        {
          if (bt.BaseType == "System.Web.Services.Protocols.SoapHttpClientProtocol")
            bt.BaseType = "MyNamespace.MySoapHttpClientProtocol";
        }
      }
    }
    

    ...

    public class MySoapHttpClientProtocol : System.Web.Services.Protocols.SoapHttpClientProtocol
    {
      protected override WebRequest GetWebRequest(Uri uri)
      {
        var req = base.GetWebRequest(uri);
        req.Headers.Add("Authorization", ...);
        return req; 
      }
    }
    

    One important thing to remember is to add the current assembly that contains "MySoapHttpClientProtocol" to CodeDomProvider references before compiling the services into assembly.

    In addition, I added

    public string AuthToken { get; set; }
    

    to MySoapHttpClientProtocol which is then injected as Authorization -header value at GetWebRequest. AuthToken can be accessed via reflection from the compiled types similar to how basic authentication was handled:

    someService.GetType().GetProperty("AuthToken").SetValue(someService, "Bearer ...");