Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OWIN multi-app bearer token authentication

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.

like image 927
Jeremy Bell Avatar asked Dec 16 '13 23:12

Jeremy Bell


3 Answers

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.

like image 181
leastprivilege Avatar answered Oct 16 '22 18:10

leastprivilege


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);
}
like image 25
Johnny Qian Avatar answered Oct 16 '22 20:10

Johnny Qian


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/

like image 40
Bryce Avatar answered Oct 16 '22 18:10

Bryce