Showing posts with label security. Show all posts
Showing posts with label security. Show all posts

Wednesday, November 12, 2025

Sitecore Forms and the Content Security Policy

Sitecore Forms and the Content-Security-Policy (CSP)

The situation

A site is using Sitecore XM/XP and Sitecore Forms and has implemented a Content Security Policy (CSP) header - which is a best practice. However, Sitecore Forms apparently does not really like these policies as they do tend to block stuff. We had a (rather big) content site that had made edits to their CSP - which is manageable in Sitecore so they can adapt it per site - based on a recent penetration test. After this extension (tightening) of the CSP we noticed the forms from Sitecore Forms were not working anymore. To be more precise: the form appeared fine, but the submit action was not working anymore when it included a "Redirect to Page" action. 

The error

Luckily the error was pretty clear in a browser console:
CSP error

The CSP header was blocking the script. 
The error mentions "Executing inline script violates the following Content Security Policy directive 'script-src ... Either the 'unsafe-inline' keyword, a hash ('sha256-Dcwc6bB3ob8DnpIRKtqhRwu0Wl6bkf7uLnQFk3g6bPQ='), or a nonce ('nonce-...') is required to enable inline execution. The action has been blocked."

The solution

As the code which outputs the inline script is in the Sitecore assemblies, it does not seem an option to add a nonce value. Adding unsafe-inline everywhere is also not a good option as that would lower the quality of the CSP dramatically. So we went for another option and tried to add this unsafe-inline only when there is a form on the page.

Adding unsafe-inline conditionally 

First of all we will add an indicator in the HttpContext to tell us whether there is a form on the page. This can be done in Form.cshtml (located in \Views\FormBuilder), which is the main cshtml file of Sitecore Forms. But as we are using SXA it can also be done in the SXA Forms wrapper. This is Sitecore Form Wrapper.cshtml (located in \Views\Shared) and as we already had some customization in this file to add translations (see my previous post on this topic) we added a few lines here:
var context = HttpContext.Current;
context.Items["WeHaveAForm"] = "Y";
You can name the context item whatever you want of course.

Now we need to act on this context item. Again, there are options. We already had some code that placed a CSP in the header based on a value set in Sitecore on the Site item. But if you do not, a generic solution would be to place it in the global_asax Application_EndRequest function.
var context = HttpContext.Current?.Items["WeHaveAForm"];
if (context != null && context.Equals("Y"))
{
    var csp = Response.Headers["Content-Security-Policy"];
    if (string.IsNullOrEmpty(csp))
    {
        return;
    }

    csp = csp.Replace("script-src", "script-src 'unsafe-inline'");
    var pattern = @"'nonce-[^']+'";
    csp = Regex.Replace(csp, pattern, string.Empty);
    Response.Headers.Set("Content-Security-Policy", csp);
}
As you can see we do just a little bit more here:
  1. We check if the item is present in the context and get out if it is not
  2. We check if we have a csp value - if not we don't need to do anything so we get out
  3. We add the 'unsafe-inline' part to the script-src, if it is present in the csp
  4. We remove the complete nonce if that is present
  5. We set the new value in the Content-Security-Policy header

It is important to also remove the nonce. When a CSP header includes both a nonce and unsafe-inline, the browser ignores the unsafe-inline for scripts or styles and uses the nonce to allow specific inline elements. So if we keep the nonce, the unsafe-inline addition will not do anything.

Conclusion

We fixed the redirects on the forms without adding unsafe-inline on all pages. I would assume that is the best solution we could find here. 

Thursday, October 10, 2024

JWT validation in Azure Web App

 JWT validation in Azure Web App

Why

First of all some background of why we are doing this. 
In a Sitecore JSS headless project we have an API which is hosted in a Web App. So no Azure Function (we have those as well) but for a number of reasons we choose a WebApi for this part. Some of these api's return sensitive data and need to be secured. And not just secured in a standard way (subscription keys, ...) but also on user level. For some requests you need to be an admin, others can only be requested for yourself and so on.

Also important to mention is that this is a B2B project where the customer decided that the login is done with an Entra App registration in order to provide SSO experience without them having to deal with granting or revoking access - except of course in the custom application itself. As a result, we have no claims in the tokens as at that moment the user is only authenticated but not yet authorized.

As we cannot fetch the role information about a user from the JWT, in our Azure API Management (apim) we can only verify if the token is valid but not the content. 

And so, we want to validate the content in the Web App itself. I found a lot of information on the subject, but nothing was really what I needed. But after a lot of trial and error and putting various docs together I finally have something that seems to work. 

As Sitecore is becoming more and more headless and distributed and custom api's are probably a part of that it seems like a good idea to share my info.

Goal

To summarize, our goal is to verify if the request came from a specific email - specific can be the same email as in the request data or the email of an admin user. Verification is done based on a JWT that will be created by MS Entra.


Code

Startup

Let's dive straight into it and start with a function that we add in our Program class:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;

void AddPolicies(WebApplicationBuilder builder)
{
  var tenant = builder.Configuration.GetValue<string>("TenantId");
  var client = builder.Configuration.GetValue<string>("ClientId");
  builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(opt =>
    {
      opt.Authority = $"https://login.microsoftonline.com/{tenant}/v2.0"; //.well-known/openid-configuration"
      opt.TokenValidationParameters = new TokenValidationParameters
      {
        ValidateIssuerSigningKey = true,
        ValidIssuer = $"https://login.microsoftonline.com/{tenant}/v2.0",
        ValidateIssuer = true,
        ValidAudience = client,
        ValidateAudience = true,
        ValidateLifetime = true,
      };
    });

  builder.Services.AddAuthorization(options =>
  {
    options.AddPolicy("EmailPolicy", policy =>
    {
      policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
      policy.Requirements.Add(new EmailRequirement());
    });
    options.AddPolicy("AdminPolicy", policy =>
    {
      policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
      policy.Requirements.Add(new AdminRequirement());
    });
  });
}
We need a tenant and client id. With those secrets we can define the Authentication based on the JwtBearer schema and set the issuing authority along with the parameters that define what we want to validate.

Once we have the authentication in place, we can add the Authorization as that is what we are actually looking for.

For the authorization we can add a Policy. Or in our case two policies as we want to be able to verify the email and the admin status. We will add the code for those policies later.

In the main function we add a call to the AddPolicies and also mention that we want to use Authentication and Authorization.
AddPolicies(builder);

app.UseAuthentication();
app.UseAuthorization();

Policies

Creating the policies is no more than creating an empty class - as example the EmailRequirement:
using Microsoft.AspNetCore.Authorization;

public class EmailRequirement : IAuthorizationRequirement
{
}

Authorization Handler

The next step - and this is where the magic happens - is the authorization handler. You have a few options here as mentioned in the MS-docs article and we decided for one handler to handle all requirements.
using Microsoft.AspNetCore.Authorization;

public class PermissionHandler : IAuthorizationHandler
{
  ...

  public Task HandleAsync(AuthorizationHandlerContext context)
  {
    if (context.Resource is HttpContext httpContext)
    {
      var jwtMail = securityService.GetJwtMail(httpContext.Request);
      if (string.IsNullOrEmpty(jwtMail))
      {
        context.Fail();
        return Task.CompletedTask;
      }

      var pendingRequirements = context.PendingRequirements.ToList();
      foreach (var requirement in pendingRequirements)
      {
        if (requirement is EmailRequirement)
        {
          var email = GetEmail(httpContext.Request);
          if (!string.IsNullOrEmpty(email) && ...)
          {
            context.Succeed(requirement);
          }
          else
          {
            context.Fail();
          }
        }
        else if (requirement is AdminRequirement)
        {
          ...
        }
      }
    }

    return Task.CompletedTask;
  }

  private static string? GetEmail(HttpRequest request)
  {
    ...
  }
}

A PermissionHandler is a straight forward class that checks the requirements sets the context to success or failure and return the task completion. In our case we are first getting the email address from the JWT (details follow) and if we have an email we loop over the requirements. 

Those are pure business logic. As an example for the EmailRequirement we fetch the email from the request (how to do this is dependent on how that information is being send to your api's). If the email is valid (equal to the one in the JWT) we set the context to be succeeded - otherwise it failed.
We repeat this for every requirement needed.

Getting the email from the JWT

using System.IdentityModel.Tokens.Jwt;

public class SecurityService : ISecurityService
{
  ...

  internal static string? GetJwt(HttpRequest request)
  {
    try
    {
      var jwt = request.Headers.Authorization;
      if (jwt.Count == 1)
      {
        var jwtSplit = jwt.ToString().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
        return jwtSplit.Last();
      }
    }
    catch
    {
      return null;
    }

    return null;
  }

  public string? GetJwtMail(HttpRequest request)
  {
    try
    {
      var jwt = GetJwt(request);
      var handler = new JwtSecurityTokenHandler();
      var token = handler.ReadJwtToken(jwt);
      var claim = token.Claims.FirstOrDefault(c => c.Type.Equals("email", StringComparison.OrdinalIgnoreCase));
      if (claim != null)
      {
        return claim.Value;
      }
    }
    catch
    {
      return null;
    }

    return null;
  }
}

The JW token is in the Authorization header. We split that string as we don't want the "Bearer" part that precedes the token. We parse the token and get the email from the claims.

Don't forget to register Permissionhandler in your main class:
builder.Services.AddScoped<IAuthorizationHandler, PermissionHandler>();

That wraps up all the code that we need to write. Now we can start using the Authorization attribute on our api's. On all task in your controller you can add the attribute with the name of the requirement that is needed:
[Authorize(Policy = "EmailPolicy")]

And that's all folks. 

I had to get quite a few blogpost and documentation pages together to get this working and I learned that any change in the architecture or setup change the way you should do this, so I hope this explanation will be useful to someone. And if not completely what you need, it might get you in the right direction.

Friday, September 13, 2024

Sitecore JSS hiding data in props

 Sitecore JSS props

A small extension to my post on Sitecore JSS and extranet security which I wrote in July. If you haven't read that one, please do so first as I will try not to repeat too much here.

What is important to mention is that we are sending roles that are allowed to see the pages along with the page data. These roles are selected in a multiselect field.  This way the front application is in charge of the security as it should. 

We noticed a problem however when we looked at the source code of the generated pages. They were listing our role id's and names. This is of course information that we would rather not show to everyone. 

Hiding page data

So our problem is data from the page that we would like to use, but not show to users.  In our case it was security related, but that could be any data of course.

We could try to change the output and adapt the way a MultilistField sends its output. I would assume the GetMultilistFieldSerializer or MultilistFieldTypeFactory might be a good start, but actually I don't want to change that behavior just for 1 field. It seems like a messy solution, especially if we would get more data like this. As it is the front app that is putting this data in it's output, it is that app's responsibility to keep it hidden.

A MultilistField will by default show the id, the url, the name, the display name and the fields of the selected items. But even if we would just have the raw value -which is the minimal that we need for our functionality- we should hide that from public eyes.

I want to mention again that I am nowhere near a NextJs expert so for me this is gibberish but my front-end companion came up with a function that is now used to filter out the "roles" field:
import { SitecorePageProps } from 'lib/page-props';

export const whiteListProps = async (props: SitecorePageProps) => {
  delete props.layoutData.sitecore?.route?.fields?.roles;
  return props;
};
In the [[...path]].tsx this function is used on the props.


As a non-expert on the matter, I was surprised to see that all data coming from Sitecore was visible in a browser. Assuming there are more non-experts out there, I hope this post makes some sense and might keep some sensitive data hidden.

Friday, July 5, 2024

Sitecore JSS extranet security

 Extranet with Sitecore JSS and NextJS


Creating an extranet in a headless setup isn't always that straight forward. It is not up to your content management system (in this case Sitecore) anymore to deliver pages and as such also not to check who is actually demanding which content.  The Sitecore security settings as we used in the XP days are not sufficient here.

In this case we also had a few particular requirements and architectural choices:
  • users are not stored through Sitecore, but in a custom database
  • roles are maintained in Sitecore (as content items)
  • pages can be visible for people from one or more roles, or even for people without a role (not yet registered guests)
  • components can have security settings that define which role(s) can view the component
  • API calls are also using the roles as security guidelines to determine who can get which data

Let's focus on the parts that get us in touch with Sitecore. So we need a solution that can set security on pages and components for roles defined and known in Sitecore. 

Pages

We created a base template with 2 fields:
  • a multiselect field to select one or multiple roles
  • a checkbox field to set the page open for everyone - including guest without a role
As we do not want to close pages for everyone (would be silly - one could unpublish the page if you want that) we also count no selected roles as all roles. 

This base template is added to all page templates. As this data is part of the page data, it is included when a page is requested by the NextJs app and can be used by that app to handle the security appropriately - send a 403 page if the page is not available for the current user.

I'm afraid I cannot share code in this article so you'll have to do with the ideas. But if you are familiar with NextJs development (which I am not btw) I would assume that this is not rocket science.

A small tip however: do not forget to check editing mode, as you do not want the security rules to be adapted when editing. 

Components

For the components, we used a similar setup. We created another base template that can be included in a component - it will not be included in all components, just in those where we want the security functionality.  This creates also a similar setup in the front app as that will receive the security data and display the components based on what it knows about the current user. 

Again, don't forget to skip the security check when editing. Although it can look weird when you have multiple components targeted at different roles, you still want to be able to edit everything.


Options

I assume there would be other options to tackle our request. It feels a lot like personalization so we also thought in that direction, but in the end our solution seemed like a simple one that would do as expected. And it is - the implementation was fairly easy and fast and the editors find their way when editing the pages and components. 

As I could only share the conceptual ideas here I hope you found it useful. And if you have any other thoughts or ideas on how to handle such requirements, I would be happy to discuss those. Always eager to learn ;) 


Monday, June 28, 2021

Transfer Sitecore security settings with Powershell

Sitecore security settings

You can set security on each item in Sitecore for any number of roles (or users, but you shouldn't do that). A while ago I had to set the security for a particular role in a way that I actually changed a few dozen items. This was on a pre-production environment and related to a new site that was prepared in that environment. That new site is moved to the production environment with a package, and that will include the security settings on all the items in the site. But what with all the ones that are not in the site and that are also touched by adding a new security rule for this role? I couldn't move all those items and I don't want to do it manually...  so I came up with a script in Powershell. I'm posting it here for my future reference, but maybe someone else can use it as well...

Sitecore Powershell Extensions (SPE) to the rescue

I created a script that created a Powershell script. Executing the created script will set all the security rules that were found for a particular role in a give path.

function GetAssignedItems($path)
{
    Write-Host "Start: " $path
     
    $items = Get-ChildItem -Path $path -Recurse
    $rootItem = Get-Item -Path $path
    $items = $items + $rootItem
 
    foreach ($item in $items)
    {
        $acl = Get-ItemAcl -Identity "MYROLE" -Item $item
        if ($acl)
        {
            foreach ($rule in $acl)
            {
                "Add-ItemAcl -PropagationType " + $rule.PropagationType + " -SecurityPermission " + $rule.SecurityPermission + " -AccessRight '" + $rule.AccessRight +"' -Identity 'MYROLE' -Path '"+ $item.Paths.FullPath + "'"
            }
        }
    }
    Write-Host "Done."
}
 
GetAssignedItems "/sitecore/media library"

Just replace "myrole" with the name of your role and that's it. The call to the function takes the root you want to check.

This will generate output like this:
Add-ItemAcl -PropagationType Entity -SecurityPermission AllowAccess -AccessRight 'item:read' -Identity 'MYROLE' -Path '/sitecore/media library/Project'
Add-ItemAcl -PropagationType Entity -SecurityPermission DenyInheritance -AccessRight '*' -Identity 'MYROLE' -Path '/sitecore/media library/Project'

You can do this for any database - just note that the database is not added to the result output. 

This actually exports the rights - meaning it will generate a script that is able to set them again on any other environment. 

To import the rights into an environment, run the resulting script in a Powershell console in your Sitecore admin. This will set the security as required. Just make sure you run the script in the correct database ;)


Monday, October 29, 2018

Sitecore 9.1 Forms - Prefilling fields & security

Sitecore 9.1 Forms

The Forms feature has 2 major new features in version 9.1 of the Sitecore platform. You'll get conditional fields and prefilling of fields. It's about that last one I wanted to give you a little bit of advice...

Prefilling fields

Without going into too much details (Sitecore 9.1 is only released in tech preview at the time of writing - release version will be available soon) the prefilling is based on value providers - classes that will get the values based on a parameter defined with the field. 

Prefilling is a nice feature, as it makes the user experience better and the conversion rate of the forms higher - visitors are lazy ;)

But.. before you jump into prefilling everything you think you know about me, read this article: https://www.campaignion.org/blog/all-you-have-know-about-pre-filling-forms

It is a good overview of what you can an can't do when prefilling forms. And there is one option related to Sitecore that I want to address.

Prefilling xConnext data

Sitecore environments usually have (a lot of) information about their visitors in the xDB database. It is very tempting to use that data to prefill the forms. But there might be catch..  

Consider the following scenario (which is not so far fetched) on a website:
  • a form with just an e-mail address to subscribe to the newsletter
  • a contact form (with email, name, address, ...)
  • all forms will identify the contact
  • all forms use prefilling on fields where possible (all ootb xXonnect fields)

So, what is the catch? Well, if I subscribe you (yes, you.. not me) to the newsletter and the system already knows you it will attach my session to your profile. No harm done, until I visit the contact form and see all your contact data. 

How do you prevent this? You probably want to keep the contact identifcation as that gives your marketers the information they want in the xConnect reports. Adding a Captcha to your form will help a bit (and this might be a good idea for other reasons as well). But actually, the only good solution is -as described in the mentioned post above- to only prefill data from a datastore (like xDB) when your user is really identified - meaning logged in or truly identified through an identifier in the querystring. 

Conclusion

Prefilling is a nice new feature (and it looks better than the WFFM version). And I hope the community will start sharing value providers. But be aware of what you do with your visitors data.. after all, you don't want to end up in a next version of Mikkel Rømer "white hat hackers" session.

Thursday, October 16, 2014

Security in ComputedIndexFields

Custom ComputedIndexField

How to create a custom ComputedIndexField in Sitecore can be found on several other location on the net. But what if you want to check the security of your item when populating this field? 

A simple function could check if your item was readable by an anonymous user:
const string anonymous = @"extranet\Anonymous";
var account = Account.FromName(anonymous, AccountType.User);
return AuthorizationManager.IsAllowed(item, AccessRight.ItemRead, account);

This will work in a normal environment, but if you try this during population of your ComputedIndexField you will always get true. Why?

SitecoreItemCrawler

The responsible crawler - Sitecore.ContentSearch.SitecoreItemCrawler - is designed in such a way to wrap the calls for your ComputedIndexField in a SecurityDisabler. So, the true value in our security check is by design.

And maybe for the best.. it seems like a much better approach to perform security checks indexed results are being accessed. And how do I do this?

Security and index results


Well, you don't. Sitecore does this for you. When you query your index, Sitecore has a Security check build in to the pipeline:  <processor type="Sitecore.Pipelines.Search.SecurityResolver, Sitecore.Kernel" /> . This checks the security of the item involved, not of any related items that you might have used to fill your ComputedIndexField. If you also want a security check on those, write a processor to extend the pipeline :)