13

I am building a multi tenant web app that connects Office 365 services using Microsoft.Owin.Security.OpenIdConnect, Version=3.0.0.0 and Azure Active Directory with Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.19.0.0 following this sample.

Our web app client (user agent) is authenticated to our server using an asp.NET cookie while the authentication between our server and authority server (Azure AD here) is made with OpenID Authorization Code Flow.

We set for the Asp.NET cookie a 30 days sliding expiration for its lifetime. However we still have a short lived AuthenticationTicket from the Authority Server even when setting UseTokenLifetime= true which is supposed to match the lifetime of the two authentication mechanisms.Short lived authentication ticket

The problem we have is: our end-users must relog frequently (less than hour). The question is then, how do we increase/change the lifetime of the authentication ticket in this owin openidconnect middleware?

REMARK: I also posted a question on the usage of refresh tokens with ADAL. From what we have understood, this problem is only related to authentication. The lifetimes of the access_token and refresh_token which is an authorization concern managed by ActiveDirectory client are independent of this problem. Correct me if I am wrong.

Startup.Auth.cs

public partial class Startup
{
  public const string CookieName = ".AspNet.MyName";
  public const int DayExpireCookie = 30;

  public void ConfigureAuth(IAppBuilder app)
  {
   app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

   var cookieAuthenticationOptions = new CookieAuthenticationOptions()
   {
       CookieName = CookieName,
       ExpireTimeSpan = TimeSpan.FromDays(DayExpireCookie),
       AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
       SlidingExpiration = true,
   };

   app.UseCookieAuthentication(cookieAuthenticationOptions);

   app.UseOpenIdConnectAuthentication(
       new OpenIdConnectAuthenticationOptions
       {
           ClientId = SettingsHelper.ClientId,
           Authority = SettingsHelper.Authority,
           ClientSecret = SettingsHelper.AppKey,
           UseTokenLifetime = true,
           TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
           {
               ValidateIssuer = false
           },

           Notifications = new OpenIdConnectAuthenticationNotifications()
           {
               // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away. 
               AuthorizationCodeReceived = (context) =>
               {
                   var code = context.Code;
                   string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
                   string signInUserId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
                   AuthenticationContext authContext = new AuthenticationContext(string.Format("{0}/{1}", SettingsHelper.AuthorizationUri, tenantID), new ADALTokenCache(signInUserId));
                   ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey);
                   // Get the access token for AAD Graph. Doing this will also initialize the token cache associated with the authentication context
                   // In theory, you could acquire token for any service your application has access to here so that you can initialize the token cache
                   Uri redirectUri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
                   AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, redirectUri, credential, SettingsHelper.AADGraphResourceId);
                   return Task.FromResult(0);
               },

               RedirectToIdentityProvider = (RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context) =>
               {
                   string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
                   context.ProtocolMessage.RedirectUri = appBaseUrl + SettingsHelper.LoginRedirectRelativeUri;
                   context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl + SettingsHelper.LogoutRedirectRelativeUri;
                   return Task.FromResult(0);
               },
               AuthenticationFailed = (context) =>
               {
                   context.HandleResponse();
                   return Task.FromResult(0);
               }
           }
       });
  }
}

Account Controller

public class AccountController : Controller
{

     public void SignIn()
     {
         var dateTimeOffset = DateTimeOffset.UtcNow;
         var authenticationProperties = new AuthenticationProperties
         {
             AllowRefresh = true,
             IssuedUtc = dateTimeOffset,
             ExpiresUtc = dateTimeOffset.AddDays(Startup.DayExpireCookie -1),
             RedirectUri = SettingsHelper.LoginRedirectRelativeUri, IsPersistent = true
         };
         HttpContext.GetOwinContext()
             .Authentication.Challenge(authenticationProperties,OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
     }

     public void SignOut()
     {
         HttpContext.GetOwinContext().Authentication.SignOut(
             new AuthenticationProperties { RedirectUri = SettingsHelper.LogoutRedirectRelativeUri,  },
             OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
     }
 }
Community
  • 1
  • 1
Benoit Patra
  • 4,355
  • 5
  • 30
  • 53
  • Note UseTokenLifetime causes the OIDC middleware to override the cookie middleware timeouts, not the other way around. – Tratcher Jan 26 '16 at 23:58
  • The lifetime of the token is controlled by AAD, not by anything you do in your own code. http://stackoverflow.com/questions/22043128/windows-azure-active-directory-expiration-of-refreshtoken – Tratcher Jan 27 '16 at 00:02
  • Thx. Your reference is dealing with the life time of the *access_token* and the *refresh_token* but now, after one hour I cannot pass the [Authorize] attribute of my controllers so I do not know how I could ask for a new token. You sure the life time of the *access_token* is the culprit for the 401 unauthorize response? It looks like related to my other question http://stackoverflow.com/questions/35017681/when-calling-acquiretokenbyrefreshtoken-on-the-authenticationcontext-instance-wi – Benoit Patra Jan 27 '16 at 08:22
  • I noticed that you set: ValidateIssuer = false. For IDP's that service multi-tenants and use the same signing token, you could be accepting tokens from another tenant. – Brent Schmaltz Jan 28 '16 at 06:47
  • Also note that Claim.FindFirst can return null. – Brent Schmaltz Jan 28 '16 at 06:48
  • @BrentSchmaltz Thx. About "ValidateIssuer", I do not understand if your comment is a warning, but I followed the advices found in the comment of my sample https://gist.github.com/bpatra/8b3cf6a1568a9ae580c2. What do you mean by token: *code authorization*, *id_token*, *access_token*, *refresh_token* ? – Benoit Patra Jan 28 '16 at 11:03
  • @BrentSchmaltz is there is a way to ensure that TenantId and NameIdentifier are indeed in the list of the claims? – Benoit Patra Jan 28 '16 at 11:04
  • I am still stuck with an authentication process which requires to authenticate again after one hour. It looks like that the problem does not come with a misusage of the *refresh_token* http://stackoverflow.com/questions/35017681/when-calling-acquiretokenbyrefreshtoken-on-the-authenticationcontext-instance-wi Is there a good reference of OpenIdConnect with Asp.net/Owin and Azure Active Directory? I do not know if my problem lies with the TokenCache or the Authorization code/Cookie managed by OpenIDConnect. – Benoit Patra Jan 28 '16 at 11:07
  • @BenoitPatra about ValidateIssuer. Some IDP's such as AzureAD, the same keys are used to sign tokens across tenants. So in order to ensure the token was issued in your tenant, you should be validating the issuer. – Brent Schmaltz Jan 28 '16 at 18:54
  • @BenoitPatra TenantId is in the tokens issued from AzureAD. Look for the 'tid' claim. For NameIdentifier you will want to use the 'sub' claim. – Brent Schmaltz Jan 28 '16 at 18:55

1 Answers1

7

Actually, I needed to set UseTokenLifetime = false. Indeed, UseTokenLifetime = true changes the internal ticket in the Asp.NET cookie to the default lifetime of access_token which is one hour. The comments from @Tratcher were true but mislead me... Yes the access_token lifetime is controlled by Azure AD and there is nothing that I can do about it. But, we implemented the refresh_token management with ADAL.NET so there is a possibility to keep authentication/authorization with Microsoft Identity server for more than one hour. Setting UseTokenLifetTime = false and use cookie authentication with 15 days sliding expiry time between my client app and my server works like a charm now.

Benoit Patra
  • 4,355
  • 5
  • 30
  • 53