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.
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.
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:
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.