November Happy Hour will be moved to Thursday December 5th.

How to authenticate into the Content Management API in a Headless CMS Optimizely environment?

Vote:
 

In a Headless environement (Optimizely Backend and NextJS), we are currently implementing content updating and viewing it on the edit page before publishing it using the Content Management API.

We are using OpenIDDict to authenticate into the API.

services.AddOpenIDConnect<SiteUser>(
               useDevelopmentCertificate: true,
               signingCertificate: null,
               encryptionCertificate: null,
               createSchema: true,
               options =>
               {

                   //options.RequireHttps = !_webHostingEnvironment.IsDevelopment();
                   var application = new OpenIDConnectApplication()
                   {
                       ClientId = "postman-client",
                       ClientSecret = "postman",
                       Scopes = {
                        "openid",
                        "offline_access",
                        "profile",
                        "email",
                        "roles",
                        "anonymous_id",
                        ContentDeliveryApiOptionsDefaults.Scope,
                        ContentManagementApiOptionsDefaults.Scope,
                        CommerceApiOptionsDefaults.Scope
                       }
                   };

                   // Using Postman for testing purpose.
                   // The authorization code is sent to postman after successful authentication.
                   application.RedirectUris.Add(new Uri("https://oauth.pstmn.io/v1/callback"));
                   options.Applications.Add(application);
                   options.AllowResourceOwnerPasswordFlow = true;
                   options.AllowAnonymousFlow = true;
               });

Using Postman we are able to call the Content Management API using the Password flow as follows:

REQUEST:

/api/episerver/connect/token

x-www-form-urlencoded body:

grant_type:password
client_id:postman-client
client_secret:postman
username:admin@example.com
password:Episerver123!

Now in our NextJS environment we are trying to call the Content Management API when the user is on the cms edit pages. However, we can't find a good way to authenticate into the API without bringing up one more login screen (Besides the Optimizely CMS Admin login) to be able to authenticate, or hardcode the credentials into our NextJS application.

Is there an optimal way to authenticate into the Content Management API in order to view the saved content before publishing it?

#312009
Edited, Nov 05, 2023 13:12
Vote:
 

If you're using the resource owner password flow, you don't need an application, client id or secret. But you need to ask the user for credentials (username and password). If you're asking the user for credentials I would recommend using the authorization code flow instread, i.e. just an application with a client id.

How are you hosting the Next.js appplication, in the same application/hostname as the CMS? If so, then I would recommend just using cookie authentication instead. Much easier and more convenient to implement. If you're hosting the Next.js app on another hostname, you can also put it behind a reverse proxy, using Yarp, in the CMS and then use cookie auth as well.

#312010
Nov 05, 2023 19:21
Vote:
 

Yes, I am using a reverse proxy and I want to use the CmsAspNetIdentity with the ContentManagementApi.

I have tried this in order to do so:

services.AddContentManagementApi(IdentityConstants.ApplicationScheme);

My CmsAspNetIdentity is set as follows:

 services.Configure<DataAccessOptions>(options => options.ConnectionStrings.Add(new ConnectionStringOptions
        {
            Name = "EcfSqlConnection",
            ConnectionString = _configuration.GetConnectionString("EcfSqlConnection")
        }));
        services.AddCmsAspNetIdentity<SiteUser>(o =>
        {
            if (string.IsNullOrEmpty(o.ConnectionStringOptions?.ConnectionString))
            {
                o.ConnectionStringOptions = new ConnectionStringOptions
                {
                    Name = "EcfSqlConnection",
                    ConnectionString = _configuration.GetConnectionString("EcfSqlConnection")
                };
            }
        });

Testing it on Postman using the cookies that I have on the browser from cms admin page (which includes .AspNetCore.Identity.Application and antiforgery) prompts me with the cms login page as a response from the api call.

Is there a way to make the CmsAspNetIdentity schema work with the ContentManagementAPI?

#312175
Edited, Nov 08, 2023 8:07
Vote:
 

I don't think you need to specify a schema at all. Have you tried just services.AddContentManagementApi();? And what error did you get, 401 or 403? The latter means you don't have sufficient permissions.

#312410
Nov 12, 2023 13:53
Taher.elhares - Nov 12, 2023 14:33
Please check my post below
Vote:
 

I did try not adding any schema but I still get the same result. 200 status code with the response being the html of the optimizely cms login page as the following:

<!DOCTYPE html>

<html lang="en">

<head id="Head1" runat="server">
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
	<link rel="shortcut icon" href="/Util/images/favicon.ico" type="image/x-icon" />
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<meta name="robots" content="noindex,nofollow" />
	<link type="text/css" rel="stylesheet" href="/Util/styles/login.css" />
</head>

<body class="epi-workspace">

	<div class="top-bar">
		<img src="/Util/images/brand-logo.svg" alt="logo" class="logo" />
		<span class="product-name">Optimizely</span>
	</div>
	<form action="/Util/Login" method="post">
		<div id="LoginControl" class="js-login-wrapper login-wrapper">
			<div class="modal">
				<ol class="clearfix">
					<li class="login-header-container"><span class="login-header">Log in</span></li>
					<li>
						<div class="text--error">
						</div>
					</li>
					<li>
						<label for="UserName">Name</label>
						<input name="UserName" type="text" id="UserName" autofocus />
                    </li>
					<li>
						<label for="Password">Password</label>
						<input name="Password" type="password" id="Password" />
                    </li>
					<li>
						<input type="submit" name="Submit" value="Log in" id="Submit" class="epi-button-child-item" />
                    </li>
					<li>
						<p class="text--small">
							<a href="#" onclick="toggleCookieText(); return false;">When you log in, cookies will be
								used.</a>
						</p>
						<div id="cookieInfoPanel" class="cookie-information">
							<p class="text--small">
								A cookie containing login information will be sent to your web browser. If you do not
								want to receive cookies, you will be unable to log into the website.
							</p>
						</div>
					</li>
					<li>
						<input id="RetunrUrl" name="ReturnUrl" type="hidden" value="/api/episerver/v3.0/contentmanagement/3270d2cd-415e-4412-ba45-133d2650916b" />
                    </li>
				</ol>
			</div>
		</div>
		<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Bd-3W3evRlFsqdDs3kz6bx7vYwkR45f8C3M1YNMSPpSGRtAlf-30xK4Dh1-RUgk0OC6RmvkGSSs0LBpHPnn6oI5yChrwbcaD8Zk6KAYfgMIOCbyTqkvw1oV0JkQCKeCInyhkkATtaC772fu8h3KmfUGLpSt_UfEW_fJGPZkcvlabn9vvJuATh1TpU3fVYGDNg" /></form>
		<script>
			var classes = [' img1', ' img2', ' img3'];
        document.getElementById("LoginControl").className += classes[Math.floor(Math.random() * classes.length)]

        function toggleCookieText() {
            document.getElementById("cookieInfoPanel").classList.toggle("is-visible");
            return false;
        }

        document.getElementById("Submit").onclick = function () {
            if (window.location.hash && window.location.hash.length > 1) {
                var encodedHash = encodeURIComponent(window.location.hash.substr(1));
                var expiresDate = new Date();
                expiresDate.setTime(expiresDate.getTime() + (5 * 60 * 1000));
                document.cookie = 'epihash=' + encodedHash + '; path=/; expires=' + expiresDate.toGMTString();
            }
        }
		</script>
</body>

</html>
#312411
Edited, Nov 12, 2023 14:33
* 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.