A private Hugo staging website on Azure Static Web Apps

Almost a year ago I decided to revive my blog and move over to Hugo. As I was already hosting my blog on Azure, it was a no-brainer to move to Azure Static Web Apps to host. At first I was planning to write a blog post about it, but the official docs are too good to pass on.

A short explainanation for anyone who hasn’t used any of the static site generators like e.g. Hugo or Jekyll before: you create your site / blog content, in Markdown for Hugo after which a process creates your static site. You can do this manually, but ideally you have some automation in place which uses a git repo and a release pipeline.

Since the very first day of moving to Hugo I have been using a second ‘hidden’ staging environment to validate my changes. I don’t want to push any changes directly to my live blog, similar to any other piece of software I ever created. Yes, you could certainly run hugo server to validate the changes locally, but this doesn’t test your pipeline. And sometimes I’m writing new content or making changes on another machine which doesn’t have Hugo installed, yet I want to see the impact.

Options for a private staging environment

Since you want your test / staging environment similar to your live production environment, I decided that everything had to run on GitHub actions and Azure Static Web Apps as well. So this gives us a few options:

  • The GitHub - Static Web Apps integration gives us a staging environment for pull requests out of the box. This has a few downsides though:
    • Your changes are in the open, as mentioned by following note: Be careful when publishing sensitive content to staged versions, as access to pre-production environments are not restricted. While usually this isn’t a problem, it is not ideal either.
    • The url changes based on your branch name. This makes testing just a tiny bit harder and requires you to create a PR.
  • IP based protection: while this might seem easy, it is also very limiting and not everyone has rather static IPs. The last thing you want is go into the portal every couple of days to change your IP.
  • Use AAD authentication for your static web app.

The last one seemed the cleanest solution for me. It gives me a secure environment with a fixed url. It requires a separate second Azure Static Web App though instead of using staging slots.

Blocking access

Since I’m using the Free tier of Static Web Apps, I can’t configure Identity through the portal. But following the documentation, you can easily block access to the entire application with a single staticwebapp.config.json file.

Since I don’t want my production environment to require authentication, and I’m using features branches and a main branch, I decided to name the file as /static/staging/staging.json. It has following content:

{
    "routes": [
      {
        "route": "/login",
        "redirect": "/.auth/login/aad",
        "allowedRoles": ["anonymous"]
      },
      {
        "route": "/.auth/login/aad",
        "allowedRoles": ["anonymous"]
      },
      {
        "route": "/logout",
        "redirect": "/.auth/logout"
      },
      {
        "route": "/*",
        "allowedRoles": ["authenticated"]
      }
    ],
    "platformErrorOverrides": [
        {
          "errorType": "NotFound",
          "serve": "/404.html"
        },
        {
          "errorType": "Unauthenticated",
          "statusCode": "302",
          "serve": "/login"
        }
      ]
  }

GitHub Actions pipelines

Next up, I duplicated the pipeline created by the Azure portal during the creating of my Hugo deployment and renamed it to Blog Staging. It triggers on features branches under posts/ and new pull requests.

An important step here is to copy the staging.json file to following path: ./static/staticwebapp.config.json. Note that Hugo uses the ./static directory to host static root files. You might wonder why I’m working with a PAT, but this is since my theme submodule repository is a private repository.

name: Blog Staging

on:
  push:
    branches:
      - posts/**

jobs:
  build_and_deploy_job:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy Job
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          token: ${{ secrets.BLOG_PAT }}
          submodules: recursive

      - name: Copy SWA config
        run: |
          mv ./static/staging/staging.json ./static/staticwebapp.config.json          
      - name: Build And Deploy
        id: builddeploy
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_STAGING }}
          repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
          action: "upload"
          app_location: "/" # App source code path
          api_location: "" # Api source code path - optional
          output_location: "public" # Built app content directory - optional

This now enables me to run staging and production pipelines depending on the branch. I can try things out without being afraid to break my live blog. E.g. in the screenshot below I tried to fix the C# url in my tag cloud and did set up this authentication protection on a separate branch. Once everything was tested on my private website, I simply fast-forwarded main to the last tested commit.

GitHub actions

Granting access

You can invite users to your Static Web App and even assign specific roles to check against. It doesn’t matter if this is a user from your own Azure Active Directory tenant or an external authentication provider like e.g. Google.

User invite

I chose to add the reader role, since everything is static anyway, and also updated staticwebapp.config.json accordingly. This brings an extra layer of security as it is no longer enough to just be authenticated.

      {
        "route": "/*",
        "allowedRoles": ["reader"]
      }

Testing

Now every time I write a new blog post or do some changes (e.g. modify the theme), I can verify on my private website. When I initially go to my private website’s URL I receive following message:

Unauthorized

The trick is going to https://yoursite.azurestaticapps.net/login instead of the root URL, as defined in the staticwebapp.config.json file. This will enable you to log in with Azure Active Directory and afterwards the page redirects to your secure private website.

Licensed under CC BY-NC-SA 4.0; code samples licensed under MIT.
comments powered by Disqus
Built with Hugo - Based on Theme Stack designed by Jimmy