4

I'm working in C# on a web application. At the moment the (bearer) authentication and token generation all happens in one place.

After the claims are done, we have the following code to get the ticket: -

var ticket = new AuthenticationTicket(identity, properties);
context.Validated(ticket);

Later on, we examine the ticket that's passed back to us using the following code to get the ticket: -

OAuthAuthenticationOptions.AccessTokenFormat.Unprotect(token);

This all works fine when the code is all hosted on the one machine.

When I split the code to work on different machines, I can't get the AuthenticationTicket back by calling the AccessTokenFormat.Unprotect method.

After reading this article OWIN Bearer Token Authentication - I tried setting the MachineKey in the web.config file of the new machine to match the MachineKey of the existing server.

The result is that the decryption process no longer throws errors, but it returns null for the token.

(When I did not have the correct machineKey, I got a decryption run-time error.)

Please could some-one let me know if there's an obvious mistake I'm making here?

Also, since I'm new to working with the OWIN pipeline; there may be a configuration step that I'm missing in the new project.

Thanks, David :-)

2016-05-23: Code from Startup.Configuration

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // Build IoC Container
        var container = new Container().Initialize();

        // Initialize Logging and grab logger.
        MyCustomLogger.Configure();
        var logger = container.GetInstance<IMyCustomLogger>();
        var userIdProvider = container.GetInstance<IUserIdProvider>();
        var azureSignalRInterface = new SignalRInterface();

        GlobalHost.DependencyResolver.Register(typeof(ITokenService), container.GetInstance<ITokenService>);
        GlobalHost.DependencyResolver.Register(typeof(IMyCustomLogger), () => logger);
        GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => userIdProvider);
        GlobalHost.DependencyResolver.Register(typeof(IExternalMessageBus), () => azureSignalRInterface);
        GlobalHost.DependencyResolver.Register(typeof(ISerializer<>), () => typeof(JsonSerializer<>));

        app.Use<ExceptionHandlerMiddleware>(logger, container);
        app.Use<StructureMapMiddleware>(container);

        // Setup Authentication
        var authConfig = container.GetInstance<OwinAuthConfig>();
        authConfig.ConfigureAuth(app);

        // Load SignalR
        app.MapSignalR("/signalR", new HubConfiguration()
        {
            EnableDetailedErrors = false,
            EnableJSONP = true,
            EnableJavaScriptProxies = true
        });
    }
}

The Container().Initialize just sets up some registries for StructureMap's dependence injection with the following code: -

    public static IContainer Initialize(this IContainer container)
    {
        container.Configure(x => {
            x.AddRegistry<ServiceRegistry>();
            x.AddRegistry<AlertsRegistry>();
            x.AddRegistry<SignalRRegistry>();
        });
        return container;
    }

Also, my Global.asax.cs file looks like this: -

    protected void Application_Start()
    {
        //GlobalConfiguration.Configure(WebApiConfig.Register);
        GlobalConfiguration.Configure(config =>
        {
            AuthConfig.Register(config);
            WebApiConfig.Register(config);
        });
    }

And the AuthConfig class looks like this: -

public static class AuthConfig
{
    /// <summary>
    /// Registers authorization configuration with global HttpConfiguration.
    /// </summary>
    /// <param name="config"></param>
    public static void Register(HttpConfiguration config)
    {
        // Forces WebApi/OAuth to handle authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
    }
}

Where OAuthDefaults.AuthenticationType is string constant.

Finally, my OwinAuthConfig code is as follows: -

public class OwinAuthConfig
{

    public static OAuthAuthorizationServerOptions OAuthAuthorizationOptions { get; private set; }
    public static OAuthBearerAuthenticationOptions OAuthAuthenticationOptions { get; private set; }

    public static string PublicClientId { get; private set; }

    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the application for OAuth based flow
        PublicClientId = "MyCustom.SignalRMessaging";
        OAuthAuthorizationOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Authenticate"), // PathString.FromUriComponent("https://dev.MyCustom-api.com/Authenticate"),
            Provider = new MyCustomDbLessAuthorizationProvider(
                PublicClientId),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),

            // TODO: change when we go to production.
            AllowInsecureHttp = true
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthAuthorizationServer(OAuthAuthorizationOptions);

        OAuthAuthenticationOptions = new OAuthBearerAuthenticationOptions
        {
            Provider = new MyCustomDbLessAuthenticationProvider()
        };

        app.UseOAuthBearerAuthentication(OAuthAuthenticationOptions);
    }

    public static AuthenticationTicket UnprotectToken(string token)
    {
        return OAuthAuthenticationOptions.AccessTokenFormat.Unprotect(token);
    }

    public void ConfigureHttpAuth(HttpConfiguration config)
    {
        config.Filters.Add(new AuthorizeAttribute());
    }
}

2016-05-26: Added config file snippets. So here's the config on the server that generates the tokens: -

  <system.web>
    <machineKey
      validationKey="..."
      decryptionKey="..." validation="SHA1" decryption="AES" />
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <customErrors mode="Off" />
  </system.web>

and here's the config on the SignalR server that tries to use the token: -

  <system.web>
    <machineKey
      validationKey="..."
      decryptionKey="..." validation="SHA1" decryption="AES" />
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5.2" />
    <httpRuntime targetFramework="4.5.2" />
    <customErrors mode="Off" />
  </system.web>
Community
  • 1
  • 1
David Mawer
  • 137
  • 1
  • 9

2 Answers2

1

In the resource server you should use OAuthBearerAuthenticationOptions.AccessTokenFormat property instead of OAuthAuthorizationServerOptions.AccessTokenFormat. See links for documentation.

For AuthenticationTokenReceiveContext in IAuthenticationTokenProvider.Receive() method you also could do context.DeserializeTicket(context.Token);.

As you pointed, the MachineKey should be the same in both servers.

I hope this helps.

EDIT (2016-05-24)

public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
    context.DeserializeTicket(context.Token);
    // Now you can access to context.Ticket
    ...
}
jumuro
  • 1,517
  • 15
  • 17
  • Hi @jumuro, I've had a look at my code to be sure that I'm using the correct `OAuthBearerAuthenticationOptions.AccessTokenFormat`. So yes, I'm using the correct class o call "AccessTokenFormat". There is a complication to my code though, I'm using StructureMap's dependence injection. So I don't know if that is confusing the issue. (I copied the code pattern from the Resource Server which also uses the same dependende injection). Do you possibly know of a tutorial / code sample where this is illustrated? – David Mawer May 10 '16 at 07:42
  • Hi @DavidMawer, I'm sorry but not used to StructureMap, I normally use Autofac. Have you tried to call `context.DeserializeTicket(context.Token);`? – jumuro May 10 '16 at 07:51
  • Hi @DavidMawer, could you please include the code of your `Startup.Configuration()` method including the OAuth configuration? Anyway you could see details of configuration including the order of middlewares in OWIN pipeline in following [post](http://bitoftech.net/2014/09/24/decouple-owin-authorization-server-resource-server-oauth-2-0-web-api/) and related ones. I hope it helps you. – jumuro May 18 '16 at 07:24
  • Hi @jamuro, I've added the code details as requested. Thanks for the pointers, for now I'm reading up on the article [post](http://bitoftech.net/2014/09/24/decouple-owin-authorization-server-resource-server-oauth-2-0-web-api/) that you provided. It's a good read. – David Mawer May 23 '16 at 10:42
  • Hi @DavidMawer, I suppose this is the code for your authorization server, that is working fine. But, could you please add the code for resource server after split to different machines? – jumuro May 23 '16 at 10:58
  • Hi @jumuro, This is the code on the resource server. I took the code from the authorization server, and copied it to the resource server. From your response - it looks like that was not such a wise thing to do. – David Mawer May 24 '16 at 06:21
  • Hi @DavidMawer, you **don't need** to execute `app.UseOAuthAuthorizationServer(OAuthAuthorizationOptions);` in resource server, thus continue executing `app.UseOAuthBearerAuthentication(OAuthAuthenticationOptions);`. In your class `MyCustomDbLessAuthenticationProvider` that implements `IAuthenticationTokenProvider` try to call `context.DeserializeTicket(context.Token);` in `ReceiveAsync` method. Please see my edited response. – jumuro May 24 '16 at 07:13
  • Hi @jumuro, OK, at last I'm starting to clean up the code, and get a feel for what's happening. So my my `MyCustomDbLessAuthenticationProvider` class implements the `OAuthBearerAuthenticationProvider` interface - not the `IAuthenticationTokenProvider` interface. When I use something that does implement the `IAuthenticationTokenProvider` interface, then I get a type error in this code: - `new OAuthBearerAuthenticationOptions { Provider = new LightSailDbLessAuthenticationProvider() };` – David Mawer May 24 '16 at 09:03
  • Hi @DavidMawer, sorry, `IAuthenticationTokenProvider` is for `AccessTokenProvider` property of `OAuthBearerAuthenticationOptions`, could you please try to set it and see if you get the token in `context.DeserializeTicket(context.Token);` in `ReceiveAsync()` method? I'm using this in one of my projects and is working fine. – jumuro May 24 '16 at 09:25
  • By the way, are you sending the token as an 'Authentication' header? I've seen that you are configuring SignalR, are you connecting with the resource server using SignalR? – jumuro May 24 '16 at 09:35
  • Hi @jumuro, OK, so one thing at a time - because these comments don't format nicely :-). I managed to get the the `ReceiveAsync()` code to execute, but when I examine `context.Ticket` I get "Null". – David Mawer May 24 '16 at 10:29
  • Hi @jumuro, for non SignalR messages the token is part of the Authentication header, for SignalR it's part of the query string. Since the system was written by different people over time, I've got code that caters for both (checks the Authentication header first, and failing that it checks the URL parameters). – David Mawer May 24 '16 at 10:31
  • Hi @DavidMawer, I've reproduced your problem getting null ticket from the token in both ways, changing the `machineKey` value in the web.config in one of the sites and changing the bearer token sent to the resource server. Could you please double check that the value of `machineKey` in both sites is the same? Please see my response [here](http://stackoverflow.com/a/37046272/2054754) with an example of `machineKey` value. – jumuro May 25 '16 at 06:42
  • Hi @jumuro, So I checked the machineKey's, and they are the same. But I did notice that my `targetFramework` is slightly different between the two servers. Is it possible that this is causing the problem? – David Mawer May 26 '16 at 07:28
  • Hi @DavidMawer, I don't think so, but at this point I'd try to set the same version for `targetFramework` and be sure that the version of Owin dll's is the same too. If you can, please confirm that the received bearer token in the resource server is the same that was generated by the authorization server, just to be sure. – jumuro May 26 '16 at 08:34
  • Hi @jumuro, Apologies for not replying sooner. It took a while to get the API projects all updated to the same technology as the new SignalR project. I still haven't finished - will get back to you as soon as I do. Thanks again for your help; I'm learning nicely, and I'm really hoping that this last different version issue solves the problem. David :-) – David Mawer May 27 '16 at 18:45
  • Hi @jumuro, Finally got to test the code with the same version of binaries on the two servers. Worked perfectly. Thanks :-) – David Mawer Aug 11 '16 at 09:41
  • Hi @DavidMawer, I'm glad that you get it working! You are welcome :-) – jumuro Aug 11 '16 at 09:52
0

Another possibilities is the machine key in the web.config has changed after the web has been deployed, causing the machine key in the compiled dll doesn't match the machine key in the dll.

Toshihiko
  • 325
  • 1
  • 8
  • 20