Migrating... how do I replace my Owin-based auth connected to a custom SSO?

Vote:
 

Hello all,

I am in the progress of migration from CMS11 to 12.  Our customer-facing site login utilizes a custom-built legacy site as a SSO, which we need to keep to authenticate. The CMS11 site uses Owin to take the resulting auth info to login to our Opti-based site.  If the user doesn't exist in Opti, it creates it.  The login is primarily used for Opti personalization and wields no power.

In CMS12, since Owin is no longer available, I need to come up with another solution for login.  I will shamefully admit that I am green in OAuth, and auth in general.  After much research, I cannot find a simple solution to this.  I see many complex examples with OpenID Connect, but it typically describes connecting with Azure AD or some other 3rd party systems, but nothing custom.

Keeping in mind that our need is Opti personalization, can anyone point me to a tutorial/example/code for CMS12?

Help me Obi-Wan, you're my only hope!

Thanks!

#302283
May 24, 2023 15:24
Vote:
 

https://docs.developers.optimizely.com/content-management-system/docs/mixed-mode-authentication#synchronize-users-and-roles-from-an-external-authentication-provider

#302286
May 24, 2023 17:36
Vote:
 

This legacy SSO, does it send a user to you as a token or how do you retreive it, headers?

OWIN is a standardized way to handle requests and responses in .net framework, there are many ways to implement middlewares that does the same thing in .net core. Assuming you can intercept the request and fetch the user in a middleware all you need to do is to ensure you can log that user in?

#302288
May 24, 2023 20:58
Vote:
 

Thank you Eric for your response and inquiry.  Our SSO does send a token via encrypted query string param, and with it w create an ApplicationUser object. Whether I use ApplicationUser or something else, I should have the user info I need.  It is from there that I am unsure how to proceed.   

I have looked at the mix-mode document Mark mentioned numerous times, and had previously settled on attempting to use the section he suggested.  But there is certainly more to it.  Is this your suggested approach as well?

From my understanding, there are two places I need to address: the Startup.ConfigureServices and the login/logout controller. 

Regarding Startup:  If I am using the example's AddOpenIdConnect(), the options definition are incomplete.  What else will I need?  Or do you have a different suggestion?

Regarding the controller:  If using OIDC, I assume I add "[Authorize(AuthenticationSchemes = "oidc")]", but what do I need to complete the actual login?  It is here I was using OWIN.

Thanks!

#302341
May 25, 2023 16:13
Eric Herlitz - May 25, 2023 18:26
Do you have an OpenID-server somewhere that generate the token to begin with?
Kevin Gainey - May 25, 2023 18:38
No. We have not used OpenID in relation to this or elsewhere. The token I referred to is a generated customer token that is specific to this SSO (old Personify tech).
Honestly, I don't know if OIDC is even the correct approach for our scenario. If there is a different way to simply sign the user into the site for Opti Personalization purposes, that's all we need.

Thanks again!
Vote:
 

You cannot use OpenID, assuming I've understood your stack you'll need to implement ASP.NET Core Identity and find a way to sign in users using a middleware. The aspnet core identity serves as a backbone to handle your users, you will also need to sync the users to opti cms using the ISynchronizingUserService.

I'll dig a bit in my projects and see if I can find something useful.

#302352
May 25, 2023 21:14
Vote:
 

To just sign in a user you don't have to do much:

var claims = new List<Claim>
{
    new Claim("name", "user@example.com")
};

var identity = new ClaimsIdentity(claims, "yada", "name", "role");
var principal = new ClaimsPrincipal(identity);

await HttpContext.SignInAsync("your-scheme", principal);

But you probably would want to implement this as a RemoteAuthenticationHandler<TOptions> or AuthenticationHandler<TOptions>.

#302408
May 26, 2023 13:02
Vote:
 

Thanks Johan for the info.  When I take what you suggested and combine it with what was already in our project, I arrived at the following

Startup.cs:

services
	.AddAuthentication()
	.AddCookie("opti-scheme")
	.AddCookie("sso-scheme");

The code where I am authenticating looks like:

// use SSO data to identify/create an ApplicationUser
var appUser = GetAppUser(ssoUser);
if (appUser != null)
{
	AddClaim(appUser, "user_id", ssoUser.CustomerId);
	AddClaim(appUser, "firstname", ssoUser.FirstName);
	AddClaim(appUser, "lastname", ssoUser.LastName);
	AddClaim(appUser, "email", ssoUser.Email);

	if (ssoUser.MemberGroups != null && ssoUser.MemberGroups.Length > 0)
	{
		SyncRoles(ssoUser.CustomerId, ssoUser.MemberGroups);
		SyncVisitorGroups(ssoUser.MemberGroups.ToArray());
	}

	var claims = _userManager.GetClaimsAsync(appUser).GetAwaiter().GetResult();
	var claimsIdentity = new ClaimsIdentity(claims, "ApplicationCookie");
	var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
	var authProps = new AuthenticationProperties { IsPersistent = false };
	_httpContext.SignInAsync("sso-scheme", claimsPrincipal, authProps).GetAwaiter().GetResult();
}
private void AddClaim(ApplicationUser appUser, string claimName, string claimVal)
{
	var claim = _userManager.GetClaimsAsync(appUser).GetAwaiter().GetResult().FirstOrDefault(x => x.Type == claimName);
	if (claim != null)
		_userManager.RemoveClaimAsync(appUser, claim).GetAwaiter().GetResult();

	_userManager.AddClaimAsync(appUser, new Claim(claimName, claimVal)).GetAwaiter().GetResult();
}

Since SignInAsync() doesn't seem to return any value, how do I know if the user is authenticated?  Does PrincipalInfo.CurrentPrincipal come into play at all?  For reference, in CMS-11 we used AuthenticationManager with Owin:

// sign-in code from CMS-11
public void SignIn(ApplicationUser user, bool isPersistent = false)
{
	var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
	authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
	var identity = _userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
	authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, identity);
}

I am still missing something.  Any help would be greatly appreciated!

Thanks, Kevin

#303976
Jun 23, 2023 18:23
Vote:
 

Since SignInAsync() doesn't seem to return any value, how do I know if the user is authenticated? 

When you're calling "SignInAsync" it means  the user has already been authenticated, and the method just creates authenticate ticket  along side the cookie. What value do you expect to return from this method? 

If I understand your current situation correctly, you have a legacy app (auth server) that allows to user to be authenticated then redirect to your opti site with a encrypted token in querystring contains user info. The code snippet your shared above with GetAppUser function, at this step, since you have received token, I assume it means the user is already authenticated. If that's not the case, how does your business want to handle the next step? E.g. redirect user  to an error page?

#304047
Jun 25, 2023 11:07
Vote:
 

Doesn't following code check if the user is authenticated?

var appUser = GetAppUser(ssoUser);
if (appUser != null) { }

Sign in, as Vincent wrote, creates an authentication ticket and persists it in a cookie (the 'sso-scheme' cookie), to keep the user signed in between page loads.

#304083
Jun 26, 2023 8:35
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.