Ed Greaves
Ed Greaves

Reputation: 4937

How do I combine OAuth and cookie authentication in Web API using OWIN?

I have a ASP.NET Web API project that is an OAuth 2 authorization provider using OWIN. Once an OAuth access token expires, we want to fall back to cookie authentication to silently renew the token without requiring the user to login. Is there a way to accomplish this?

We are using the implicit grant flow from an Angular page to access the API. We want to have the tokens expire on a regular basis as we feel this is more secure. We want to use the cookie issued by the login page to allow us to issue tokens for a while without interactive login.

We have this in Startup.cs:

public void Configuration(IAppBuilder app)
{
    var webConfiguration = WebConfiguration.Initialize(WebConfigurationManager.AppSettings);
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        AuthenticationMode = AuthenticationMode.Passive,
        LoginPath = new PathString("/logon"),
        ExpireTimeSpan = webConfiguration.Authentication.CookieExpirationTimeSpan
    });

    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

    app.UseOAuthAuthorizationServer(
        new OAuthAuthorizationServerOptions
        {
            AllowInsecureHttp = true,
            AuthorizeEndpointPath = new PathString("/oauth/authorize"),
            AccessTokenExpireTimeSpan = webConfiguration.OAuth.AccessTokenExpirationTimeSpan,
            Provider = new LegacyAuthorizationServerProvider(
                new OAuthClientValidator(new WebConfigurationOAuthClientSource(webConfiguration)),
                new LegacyAuthenticator(new AuthenticationResultHandlerFactory(), new RefreshTokenResultHandler())),
        });
    var httpConfiguration = new HttpConfiguration();

    httpConfiguration.SuppressDefaultHostAuthentication();

    httpConfiguration.Filters.Add(new HostAuthenticationAttribute(OAuthDefaults.AuthenticationType));

}

Then in our LegacyAuthorizationServerProvider we have this:

public class LegacyAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    ...
    private async Task Authorize(OAuthAuthorizeEndpointContext context)
    {
        if (context == null) { throw ArgumentIs.Null(nameof(context)); }

        var authenticateResult = await context.OwinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie);
        if (authenticateResult?.Identity == null)
        {
            context.OwinContext.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie);
        }
        else
        {
            var claimsIdentity = new ClaimsIdentity(
                authenticateResult.Identity.Claims,
                "Bearer",
                "sub",
                "role");

            context.OwinContext.Authentication.SignIn(claimsIdentity);
        }

        context.RequestCompleted();
    }

We have set the AccessTokenExpirationTimeSpan to 15 mins and the CookieExpirationTimeSpan to 10 hours with the intention of issuing a token without login until the cookie expires. The problem is that the AuthenticateAsync() returns null after the OAuth access token expires. It only succeeds if we call authorize before the token has expired.

FYI, here is the code in the login page controller that signs in:

    [HttpPost]
    [ActionName("logon")]
    public ActionResult LogOnPost(LogOnFormModel form)
    {
        if (form == null) 
        { 
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest, Resources.AuthenticationController_LogOnPost_NullFormResultMessage); 
        }

        var context = new LegacyAuthenticationContext(form.UserName, form.Password);

        _legacyAuthenticator.Authenticate(context);

        if (context.Authenticated)
        {
            var claimsIdentity = new ClaimsIdentity(
                context.CreateClaims(),
                "sub",
                "role");

            HttpContext.GetOwinContext().Authentication.SignIn(claimsIdentity);
        }
        return LogOn();
    }

Is there a way we can use the cookie to authenticate in lieu of having the user login (when we issue another OAuth token)? Once the cookie expires we would require another login.

Upvotes: 3

Views: 2259

Answers (1)

Ed Greaves
Ed Greaves

Reputation: 4937

What we found out is the cookies expiration was being overwritten at some point. We are still not sure why. However, we are now using this work-around:

    private class CookieProvider : CookieAuthenticationProvider
    {
        public CookieProvider()
        {
            OnResponseSignIn = SignIn;
        }

        private void SignIn(CookieResponseSignInContext obj)
        {
            if (obj.Properties.IssuedUtc.HasValue)
            {
                obj.Properties.ExpiresUtc = obj.Properties.IssuedUtc.Value.Add(obj.Options.ExpireTimeSpan);
            }
        }
    }

We add in this provider in Startup.Auth.cs as part of the CookieAuthenticationOptions.Provider

Upvotes: 0

Related Questions