I have an application that compiles service reference assembly on-the-fly for given WSDL address:
Use System.Net.WebClient to OpenRead the target service as Stream
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);
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.
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 ...");