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

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?

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?
Tell OWIN that you are using the Google authentication during startup.
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.
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:
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);
}
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
}
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);
}

Related

Google service account: Delegate Domain-Wide Delegation of Authority to impersonate only ONE user

We need to access Google Calendar API with server application to create the events and invite the attendees.
Google recommends to use service account for the applications.
The main problem here with the attendees inviting to the event, because the service account can't do it without the Domain-Wide Delegation of Authority (see img).
Organization do not want to give the service account an access to ALL user's data. So, I'm trying to find out can we delegate the Domain-Wide Delegation of Authority to the ONE user of the domain ? (restrict access to use another user's data).
p.s. it's only about google calendar api.
Many thanks for the help.
I am still not sure that this question is programming related, however if you just want to see "in code" how to delegate to a user here is an example using C# where the gsuiteUser is added to the code where the ServiceAccountCredential is initialized.
When run this code will run as if the code is being run by the gsuiteuser. there for any access that that user has on the gsuite domain the service account will have. There is no way to limit that access anymore then that. Service accounts are dummy users who can be preauthorized to have access of a user.
string ApplicationName = "Calendar API .NET Quickstart";
const string serviceAccount = "xxx#xxxxx-api.iam.gserviceaccount.com";
var certificate = new X509Certificate2("cred.p12", "notasecret", X509KeyStorageFlags.Exportable);
var gsuiteUser = "user#mygsuitedomain.com";
var serviceAccountCredentialInitializer = new ServiceAccountCredential.Initializer(serviceAccount)
{
User = gsuiteUser, // Service account will run as this user
Scopes = new[] { CalendarService.Scope.read }
}.FromCertificate(certificate);
var credential = new ServiceAccountCredential(serviceAccountCredentialInitializer);
if (!credential.RequestAccessTokenAsync(CancellationToken.None).Result)
throw new InvalidOperationException("Access token failed.");
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});

Azure Active Directory SSO with MSAL and openID Connect

I was tasked with writing an ASP.NET website that uses Azure Active Directory. I went with the route of OAuth and OpenID Connect. I am not able to use implicit flow and therefore must set the ResponseType to be code.
Using MSAL code samples I got most of it working but the problem is that all the samples are using a response type that returns tokens. I think I need to do it in 2 separate steps, first get the authorization code and then get the id token. I'm not exactly sure how to do this and would much appreciate some guidance here.
I have a startup class that look like this:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = authority,
ClientId = clientId,
RedirectUri = redirectUri,
Scope = "openid profile email offline_access user.readbasic.all", // a basic set of permissions for user sign in & profile access
ResponseType = OpenIdConnectResponseType.Code,
ClientSecret = clientSecret,
TokenValidationParameters = new TokenValidationParameters
{
// In a real application you would use ValidateIssuer = true for additional checks and security.
ValidateIssuer = false,
NameClaimType = "name",
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
}
});
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
// Handle any unexpected errors during sign in
context.OwinContext.Response.Redirect("/Error?message=" + context.Exception.Message);
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
/*
The `MSALPerUserMemoryTokenCache` is created and hooked in the `UserTokenCache` used by `IConfidentialClientApplication`.
At this point, if you inspect `ClaimsPrinciple.Current` you will notice that the Identity is still unauthenticated and it has no claims,
but `MSALPerUserMemoryTokenCache` needs the claims to work properly. Because of this sync problem, we are using the constructor that
receives `ClaimsPrincipal` as argument and we are getting the claims from the object `AuthorizationCodeReceivedNotification context`.
This object contains the property `AuthenticationTicket.Identity`, which is a `ClaimsIdentity`, created from the token received from
Azure AD and has a full set of claims.
*/
IConfidentialClientApplication confidentialClient = GroupManager.Utils.MsalAppBuilder.BuildConfidentialClientApplication(null);
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(new[] { "openid profile email offline_access user.readbasic.all" }, context.Code).ExecuteAsync();
}
How do I take the information from the result's tokens and create a claims identity for the AuthenticationTicket.Identity and access the user info?
Please note that this is an ASP.NET application. Not MVC and not Core.
If you use MSAL, you don't need to handle the code yourself. MSAL will return the token to you after you log in interactively, please see:Overview of Microsoft Authentication Library (MSAL).
Before that, you need to take a look at Add sign-in to Microsoft to an ASP.NET web app,the workflow is:
Code example please check: https://github.com/AzureAdQuickstarts/AppModelv2-WebApp-OpenIDConnect-DotNet
Update:
Try to enable ID token

Port over existing MVC user authentication to Azure functions

I have an old web application which is using ASP.net with the build in cookie based authentication which has the standard ASP.net SQL tables for storing the users credentials.
This is currently running as an Azure web app, but I was toying with the idea of trying to go serverless as per this example creating a ReactJs SPA hosting on blob storage to try and keep costs down and also improve performance without breaking the bank.
https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/serverless/web-app
I was wondering if it is possible to port over the existing ASP.net authentication to Azure functions, to instead return a JWT (JSON Web Token) which could be passed back in the headers to handle authenticated requests.
When I have tried this in the past I have failed misserably, so I was wondering if anyone knows if it is possible?
I've seen this article, which seems to talk about Azure functions doing authentication, but with Azure AD, which I don't think is right for what I need.
https://blogs.msdn.microsoft.com/stuartleeks/2018/02/19/azure-functions-and-app-service-authentication/
The answer is kind of. What I mean by this is that you can use your existing database and many of the same libraries, but you can't port over the code configuration. The default authentication for Functions is either 1) The default API tokens or 2) one of the EasyAuth providers baked into App Services which is in the guide you linked. Currently, any other solution you'll need to setup yourself.
Assuming you go with the JWT option, you'll need to turn off all of the built-in authentication for Functions. This includes setting your HttpRequest functions to AuthorizationLevel.Anonymous.
At a basic level You'll need to create two things. A function to issue tokens, and either a DI service or a custom input binding to check them.
Issuing tokens
The Functions 2.x+ runtime is on .NET Core so I'm gong to borrow some code from this blog post that describes using JWTs with Web API. It uses System.IdentityModel.Tokens.Jwt to generate a token, which we could then return from the Function.
public SecurityToken Authenticate(string username, string password)
{
//replace with your user validation
var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password);
// return null if user not found
if (user == null)
return null;
// authentication successful so generate jwt token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
return tokenHandler.CreateToken(tokenDescriptor);
}
Validating Tokens
There are several guides out there for validating JWT within Azure Functions. I like this one from Ben Morris: https://www.ben-morris.com/custom-token-authentication-in-azure-functions-using-bindings/ (source code). It describes authenticating with either a custom input binding or with DI. Between the two, DI is the preferred option, unless there is a specific reason you need to use a binding. Here again, its the Microsoft.IdentityModel.JsonWebTokens and System.IdentityModel.Tokens.Jwt libraries that you'll need to do the bulk of the work.
public class ExampleHttpFunction
{
private readonly IAccessTokenProvider _tokenProvider;
public ExampleHttpFunction(IAccessTokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
[FunctionName("ExampleHttpFunction")]
public IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "example")] HttpRequest req, ILogger log)
{
var result = _tokenProvider.ValidateToken(req);
if (result.Status == AccessTokenStatus.Valid)
{
log.LogInformation($"Request received for {result.Principal.Identity.Name}.");
return new OkResult();
}
else
{
return new UnauthorizedResult();
}
}
}

Aps .net IdentityServer4 authorize

I'm using IdentityServer4 with asp .net identity as authentication point. My APIs/WebApps call identity server to get access token.
Now, how to authorize uses before some action or inside action in my api/app controller?
I can add roles to access token and then in controller (in web api/web app) use AuthorizeAttribute and check if user IsInRole.
But it means that if I will change user roles, he will see it after logout-login (because roles are part of access token) or token has to expire.
I would like to ask identity server about user role(s) each time I need to authorize him to some action (especially to action like modify/delete some data).
Question how?
Or What I have to looking for?
So there's a few possible solutions here:
Make a call to the OIDC UserInfo Endpoint to obtain updated user claims on every request
Lower the cookie lifetime to refresh user info automatically more often
Implement a custom endpoint on IdentityServer for it to post profile change information to a list of subscribed clients (such as your webapp).
Have IdentityServer force single sign out when user profile data is changed
In terms of difficulty to implement, lowering cookie lifetime is the easiest (just change cookie expiration), but it doesn't guarantee up-to-date claims, and it is visible to the user (frequent redirects to IdentityServer, although no login is required if the access token lifetime is still valid)
Having the webapp call the UserInfo Endpoint on each request is the next easiest (see sample below) but has the worst performance implications. Every request will produce a round trip to IdentityServer.
The endpoint / subscriber model would have the lowest performance overhead. UserInfo requests to IdentityServer would ONLY occur when user profile information has actually changed. This would be a bit more complicated to implement:
On your IdentityServer project, you would need to modify changes to profile data, and post an http message to your webapp. The message could simply contain the user ID of the modified user. This message would need to be authenticated somehow to prevent malicious users from voiding legitimate user sessions. You could include a ClientCredentials bearer token for this.
Your webapp would need to receive and authenticate the message. It would need to store the changed user's ID somewhere accessible to the OnValidatePrincipal delegate (through a service in the DI container most likely)
The Cookie OnValidatePrincipal delegate would then inject this local service to check if user information has changed before validating the principal
Code Samples
Get updated UserInfo from endpoint on each call
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get updated UserInfo from IdentityServer
var accessToken = context.Principal.Claims.FirstOrDefault(c => c.Type == "access_token").Value;
var userInfoClient = new UserInfoClient("https://{IdentityServerUrlGoesHere}");
var userInfoResponse = await userInfoClient.GetAsync(accessToken);
// Invalidate Principal if Error Response
if (userInfoResponse.IsError)
{
context.RejectPrincipal();
await context.HttpContext.Authentication.SignOutAsync("NameOfYourCookieAuthSchemeHere");
}
else
{
// Check if claims changed
var claimsChanged = userInfoResponse.Claims.Except(context.Principal.Claims).Any();
if (claimsChanged)
{
// Update claims and replace principal
var newIdentity = context.Principal.Identity as ClaimsIdentity;
newIdentity.AddClaims(userInfoResponse.Claims);
var updatedPrincipal = new ClaimsPrincipal();
context.ReplacePrincipal(updatedPrincipal);
context.ShouldRenew = true;
}
}
}
}
});
Update On Subscribed Change Message from IdentityServer. This example supposes you've created a service (ex IUserChangedService) which stores userIds received at the endpoint from IdentityServer. I don't have samples of the webapp's receiving endpoint or a service.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get User ID
var userId = context.Principal.Claims.FirstOrDefault(c => c.Type == "UserIdClaimTypeHere");
var userChangedService = context.HttpContext.RequestServices.GetRequiredService<IUserChangedService>();
var userChanged = await userChangedService.HasUserChanged(userId);
if (userChanged)
{
// Make call to UserInfoEndpoint and update ClaimsPrincipal here. See example above for details
}
}
}
});
The asp.net core docs have an example of this as well, except working with a local database. The approach of wiring to the OnValidatePrincipal method is the same:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie#reacting-to-back-end-changes
Hope this helps!

Replacing Cookie by Token based authentication in ASP.NET OWIN OpenIdConnect code authorization flow

We have a web application written in ASP.NET that uses MVC for serving our Single Page Applications and Web API for ajax calls.
The authentication uses Microsoft.Owin and OpenIdConnect with Azure AD for Authority. The OAUTH flow is server side code authorization.
Then in Startup.Auth.cs we have
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
var cookieAuthenticationOptions = new CookieAuthenticationOptions()
{
CookieName = CookieName,
ExpireTimeSpan = TimeSpan.FromDays(30),
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
SlidingExpiration = true,
};
app.UseCookieAuthentication(cookieAuthenticationOptions);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthorizationCodeReceived = (context) =>
{
/*exchange authorization code for a token
stored on database to access API registered on AzureAD (using ADAL.NET) */
},
RedirectToIdentityProvider = (RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context) =>
{
/* Set the redirects URI here*/
},
});
}
When clicking on signin we navigate to an url whose routes map to the methods of the following MVC controller
public class AccountController : Controller
{
public void SignIn(string signalrRef)
{
var authenticationProperties = /* Proper auth properties, redirect etc.*/
HttpContext.GetOwinContext()
.Authentication.Challenge(authenticationProperties, OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
public void SignOut(string signalrRef)
{
var authenticationProperties = /* Proper auth properties, redirect etc.*/
HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
Then the end-user connected to our application is authenticated between our client apps and the ASP.net server using an ASP.NET cookie. We would like to use Token Based approach instead. If you are interested this is the reason.
I tried to replace
the Nuget package Microsoft.Owin.Security.Cookies by Microsoft.Owin.Security.OAuth and in Startup.cs
replacing
app.UseCookieAuthentication(cookieAuthenticationOptions); by app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
and in my AccountController we changed the challenge from HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType); to HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, OAuthDefaults.AuthenticationType);
The problem is that with Cookie the set-cookie was automatically sent in web request respond when the flow completes while redirecting to the url we specified.
Where can I find the Bearer generated by OWIN with UseOAuthBearerAuthentication (if there is any) **, **Where and When should I send it back to my client SPAs
Note: an open source sample of what we are trying to do can be found in this github repository.
I think there are two approaches for you to consider.
Use javascript libraries to perform sign-in & token acquisition within your single page app. Then your backend is purely an web API, and can just use OAuth bearer middleware to authenticate requests. The backend doesn't know anything about signing the user in. We have a good sample that takes this approach here. If your backend needs to make API calls as well, you could consider the OnBehalfOf flow as well. I usually recommend this approach.
Use the OpenIDConnect middleware in your server to perform user sign-in and token acquisition. You might even be able to omit the usage of the CookieAuthenticationMiddleware entirely (although I'm not 100% sure). You can capture the token in the AuthorizationCodeReceived notification as you mention, and you could redirect back to your SPA with the token in the fragment of the URL. You could also have some route which delivers the tokens (which are cached on your server) down to your javascript. In either case, you'll need to ensure that an outside caller can't get access to your tokens.
The thing to keep in mind will be how you refresh tokens when they expire. If you use #1, most of it will be handled for you by libraries. If you use #2, you'll have to manage it more yourself.

Resources