OWIN multi-app bearer token authentication

11,234

Solution 1

Currently the middleware (or rather the produced token) is not really designed to work cross-application. For these scenario you should rather use a real authorization server (e.g. https://github.com/thinktecture/Thinktecture.AuthorizationServer).

That said you might get it to work by synchronizing the machine key (machineKey element in web.config) in both applications. But I never tried it.

Solution 2

By default, OWIN use ASP.NET machine key data protection to protect the OAuth access token when hosted on IIS. You can use MachineKey class in System.Web.dll to unprotect the tokens.

public class MachineKeyProtector : IDataProtector
{
    private readonly string[] _purpose =
    {
        typeof(OAuthAuthorizationServerMiddleware).Namespace,
        "Access_Token",
        "v1"
    };

    public byte[] Protect(byte[] userData)
    {
       throw new NotImplementedException();
    }

    public byte[] Unprotect(byte[] protectedData)
    {
        return System.Web.Security.MachineKey.Unprotect(protectedData, _purpose);
    }
}

Then, construct a TicketDataFormat to get the AuthenticationTicket object where you can get the ClaimsIdentity and AuthenticationProperties.

var access_token="your token here";
var secureDataFormat = new TicketDataFormat(new MachineKeyProtector());
AuthenticationTicket ticket = secureDataFormat.Unprotect(access_token);

To unprotect other OAuth tokens, you just need to change the _purpose content. For detailed information, see OAuthAuthorizationServerMiddleware class here: http://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.OAuth/OAuthAuthorizationServerMiddleware.cs

if (Options.AuthorizationCodeFormat == null)
{
    IDataProtector dataProtecter = app.CreateDataProtector(
        typeof(OAuthAuthorizationServerMiddleware).FullName,
        "Authentication_Code", "v1");

    Options.AuthorizationCodeFormat = new TicketDataFormat(dataProtecter);
}
if (Options.AccessTokenFormat == null)
{
    IDataProtector dataProtecter = app.CreateDataProtector(
        typeof(OAuthAuthorizationServerMiddleware).Namespace,
        "Access_Token", "v1");
    Options.AccessTokenFormat = new TicketDataFormat(dataProtecter);
}
if (Options.RefreshTokenFormat == null)
{
    IDataProtector dataProtecter = app.CreateDataProtector(
        typeof(OAuthAuthorizationServerMiddleware).Namespace,
        "Refresh_Token", "v1");
    Options.RefreshTokenFormat = new TicketDataFormat(dataProtecter);
}

Solution 3

While the answers currently listed are pretty good, I've used the following a few times and had great success. Setting the machine key in web.config works well. Make sure you use the powershell from the microsoft site to generate your own! http://bitoftech.net/2014/09/24/decouple-owin-authorization-server-resource-server-oauth-2-0-web-api/

Share:
11,234
Jeremy Bell
Author by

Jeremy Bell

Areas of interest: C++, C# and .NET, Windows Forms, WPF, Silverlight, python, ruby and rails, ASP.NET, XNA, Windows Phone 7 development

Updated on July 24, 2022

Comments

  • Jeremy Bell
    Jeremy Bell almost 2 years

    I want to make a modification to the default single page application template for ASP.NET in VS 2013, which currently uses bearer token authentication. The sample uses app.UseOAuthBearerTokens to create both the token server and the middleware to validate tokens for requests in the same app.

    What I'd like to do is leave that in place, but add a second application (bound in IIS to the same domain, different path - e.g. /auth/* for the authentication server, and /app1/* for the app). For the second app, I want it to accept tokens issued by the authentication server in the first app. How could this be accomplished? I have tried the following in Startup.Auth.cs just going off of the code in UseOAuthBearerTokens, but I get 401 responses to any requests with the [Authorize] attribute:

    public partial class Startup
    {
        static Startup()
        {
            PublicClientId = "self";
    
            UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
    
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                //TokenEndpointPath = new PathString("/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
                //AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                //AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
                AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
                AuthenticationType = "ExternalBearer",
                AllowInsecureHttp = true,
            };
        }
    
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
    
        public static Func<UserManager<IdentityUser>> UserManagerFactory { get; 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)
        {
            //// Enable the application to use a cookie to store information for the signed in user
            //// and to use a cookie to temporarily store information about a user logging in with a third party login provider
            //app.UseCookieAuthentication(new CookieAuthenticationOptions());
            //app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
    
            OAuthBearerAuthenticationOptions bearerOptions = new OAuthBearerAuthenticationOptions();
            bearerOptions.AccessTokenFormat = OAuthOptions.AccessTokenFormat;
            bearerOptions.AccessTokenProvider = OAuthOptions.AccessTokenProvider;
            bearerOptions.AuthenticationMode = OAuthOptions.AuthenticationMode;
            bearerOptions.AuthenticationType = OAuthOptions.AuthenticationType;
            bearerOptions.Description = OAuthOptions.Description;
            bearerOptions.Provider = new CustomBearerAuthenticationProvider();
            bearerOptions.SystemClock = OAuthOptions.SystemClock;
            OAuthBearerAuthenticationExtensions.UseOAuthBearerAuthentication(app, bearerOptions);
        }
    }
    
    public class CustomBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
        {
            public override Task ValidateIdentity(OAuthValidateIdentityContext context)
            {
                var claims = context.Ticket.Identity.Claims;
                if (claims.Count() == 0 || claims.Any(claim => claim.Issuer != "LOCAL AUTHORITY"))
                    context.Rejected();
                return Task.FromResult<object>(null);
            }
        }
    

    Obviously I'm missing the part where the second app has some way of validating that the tokens came from the first app. A public signing key of some kind?

    This is just for a proof of concept.

    Edit: The machine key suggestion worked well enough for the POC demo, and it's good to know there are AS implementation options that support other key scenarios.

    I was able to generate a DEMO key (do not use for production) using this site: http://aspnetresources.com/tools/machineKey

    And placed the result under the <system.web> element in the web.config of each app hosted in the IIS site. I also had to remove some of the AS-specific config options in the Startup class of the resource server.

  • Dunc
    Dunc about 9 years
    +1 for using the same machine key across a web farm, to allow the same access/bearer token on multiple servers. Today I learned "the default data protection provider ... on IIS will use ASP.NET machine key data protection" msdn.microsoft.com/en-us/library/…
  • Damon Drake
    Damon Drake over 6 years
    not +1 for using machine key. Been fighting this stuff all afternoon and machine key is not going to fix my issue. Just rolled my own IDataProtector and trying to figure out how to get it all in the chain and every sample I come across people puke out machine key like it's the holy grail. Lame!