Search code examples
asp.netasp.net-identityowin

Use two Owin identities in the same asp.net web application


Is it possible to use OWIN with two different authentications in the same time, e.g. Microsoft and Google?

In my ASP.NET web application, users authenticate initially with Azure OpenIdConnect to use the application.

At some point, user needs to authenticate with Google to perform few queries with Google (without overriding the Microsoft identity that will continue to be used).

I noticed that whenever I use Context.GetOwinContext().Authentication.Challenge(properties, "Google"), the authentication succeeded and I can call Google's API, but the Microsoft related claims, tokens and the whole identity are lost and replaced with the Google one, and I cannot anymore call Microsoft API unless I ask users to login again.

Is there any way to hold both identities so I can use them based on the need?


Solution

  • Since there was no answers and I could figure out the solution, Thanks to OWIN team support on GitHub, below is the solution:

    Objective: Authenticate with more than one provider, and maintain both claims, so application can call both providers API's at any time. In my case, Users must authenticate first with Azure Active Directory (OpenIdConnect) to be allowed to enter my application, plus to call Microsoft Graph API. Users also need to authenticate with Google, to make calls to Google API.

    For OpenIdConnect, I'm using the default creation by visual studio without any changes, and this is not the topic here.

    How to add the second provider?

    1. Tell OWIN that you are using the Google authentication during startup.
    2. When user is trying to call Google API, check if it has Google related claims. if yes, use the access token and simply call the Google API, if Not, it means this is the first call to Google, so ask the application to authenticate with Google.
    3. Once Authenticated with Google, save the claims (without overwriting OpenIdConnect's claims), so it can be used next time when calling Google API.

    Now, let's see the details:

    1. Below the main authentication (OpenIDConnect), Tell OWIN that you are using Google (by the way, this applies to any other provider). The most important part, is to tell OWIN to use different cookies for Google to save Google's claims in separate cookies. If you miss this step, the Google claims will overwrite the OpenIdConnect Claims, and you will not be able to call Microsoft Graph anymore.

      public void ConfigureAuth(IAppBuilder app)
      {
          app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
      
          app.UseCookieAuthentication(new CookieAuthenticationOptions()
          {
      
          });
          app.UseOpenIdConnectAuthentication(
              new OpenIdConnectAuthenticationOptions
              {
                  ClientId = AuthenticationConfig.ClientId,
                  Authority = AuthenticationConfig.Authority,
                  PostLogoutRedirectUri = AuthenticationConfig.PostLogoutRedirectUri,
                  RedirectUri = AuthenticationConfig.RedirectUri,
                  Scope = $"{AuthenticationConfig.BasicSignInScopes} User.Read",
                  SaveTokens=true,
                  TokenValidationParameters = new TokenValidationParameters
                  {
                      ValidateIssuer = false,
                  },
                  Notifications = new OpenIdConnectAuthenticationNotifications
                  {
                      AuthenticationFailed = OnAuthenticationFailedAsync,
                      AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync,
                      SecurityTokenValidated = OnSecurityTokenValidatedAsync,
                  },
                  CookieManager = new Utils.SameSiteCookieManager(
                                   new SystemWebCookieManager())
      
              }
              );
          // Define New Cookies for Google
          app.UseCookieAuthentication(new CookieAuthenticationOptions
          {
              AuthenticationType = "Google",
              AuthenticationMode = AuthenticationMode.Passive,
              CookieName = CookieAuthenticationDefaults.CookiePrefix + "External.DocuSign",
          });
          // Tell OWIN to use Google with the special Cookies type
          app.UseGoogleAuthentication(new Microsoft.Owin.Security.Google.GoogleOAuth2AuthenticationOptions()
          {
              ClientId = "xxxxxxxxxxxxxxxxxxxxxxx",
              ClientSecret = "xxxxxxxxxxxxxxxxxx",
              SignInAsAuthenticationType = "Google",
              Provider = new Microsoft.Owin.Security.Google.GoogleOAuth2AuthenticationProvider() { OnAuthenticated = OnGoogleAuthenticated }
          });
          // This makes any middleware defined above this line run before the Authorization rule is applied in web.config
          app.UseStageMarker(PipelineStage.Authenticate);
      }
      
    2. Before calling Google API, Check if Google Claims already exist. If yes, extract the access token and call Google API. If not, it means this is the first time you're trying to call Google, so authenticate first, save the claims, and then call the API.

          var result = await Request.GetOwinContext().Authentication.AuthenticateAsync("Google");
          if (result == null) // No Claims found for Google
          {
              // Redirect to Google for authentication
              var properties = new AuthenticationProperties() { RedirectUri = "/" };
              Context.GetOwinContext().Authentication.Challenge(properties, "Google");
          }
          else
          {
              // Get the Access Token from the google Claims
              var accessToken = result.Identity.Claims.FirstOrDefault(a => a.Type == "google_access_token").Value;
              // Now CALL Google API
          }
      
    3. Save the Google Claims after authenticating with Google. This is again in StartupAuth.cs in continuation to app.UseGoogeAuthentication where we override the event of getting google response, and we save the token to claims.

          private static Task OnGoogleAuthenticated(Microsoft.Owin.Security.Google.GoogleOAuth2AuthenticatedContext context)
      {
          // Save the access token to Google Claims, to be used in Google API calls
          context.Identity.AddClaim(new Claim("google_access_token", context.AccessToken));
      
          if (context.RefreshToken != null)
          {
              context.Identity.AddClaim(new Claim("google_refresh_token", context.RefreshToken));
          }
          var expiresInSec = (long)(context.ExpiresIn.Value.TotalSeconds);
          context.Identity.AddClaim(new Claim("google_expires_in", expiresInSec.ToString()));
          return Task.FromResult(0);
      }