Valid Token, Forbidden Anyway: What an SEO Automation Build Taught Me About IAM
I set out to build an n8n workflow that would scan my website for SEO issues once a week. Fourteen pages, a sitemap, a scoring rubric. Simple enough. Two days later it had grown into a 19-node pipeline pulling from Search Console, Google Analytics, and OpenAI, and every hard problem I hit along the way turned out to be an identity and access problem wearing an SEO costume.
That is the part worth writing about. Not the SEO scores. The credential handling.
The client secret that was gone for good
The Google Cloud OAuth client I needed had been created back in February. The secret that went with it was nowhere to be found, and Google does not let you view a secret again once it is generated. Only two options exist: reset it or add a new one alongside it.
I added a new one. Resetting would have killed anything already running against the old secret, and I had no way to confirm nothing was. Additive rotation cost nothing and broke nothing. It is the same call you make rotating a service account key in production: never assume you are the only consumer of a credential, even when you are pretty sure you are.
A valid token that still got rejected
Search Console kept returning "Forbidden, insufficient permission" on a token that authenticated cleanly. That gap, a valid token but a denied action, is the whole authentication-versus-authorization distinction in one error message. The API had let me in the door and then stopped me at the room I wanted.
Calling the sites-list endpoint directly surfaced the actual cause: permissionLevel: siteUnverifiedUser. The domain property existed in Search Console, but DNS ownership verification had never been completed. The account was real. The access was not granted. Fixing it meant finishing domain verification, not touching the token at all.
Authentication answers "who are you." Authorization answers "what are you allowed to do." A clean login tells you nothing about the second question.
Scopes added one at a time, not all at once
The credential started with webmasters.readonly because that is all the first version of the workflow needed. When I added Google Analytics data later, I added the analytics.readonly scope and went through re-consent rather than requesting everything up front in case I needed it later. Drive got its own separate credential entirely, scoped to just what the file-save step required.
Five credentials, four Google APIs, one OpenAI connection, all wired into a single automated identity, each piece holding only what it needed to do its one job. That is what least-privilege access control looks like day to day: small, specific grants, added only when a new piece of work actually needs them.
Sessions expire mid-task, always plan for it
Partway through editing the workflow, n8n's autosave started failing with "Unauthorized." The login session had quietly expired underneath the editor while I kept working. Nothing was wrong with the workflow logic, the session token backing my browser tab had simply timed out, the same as it does for any user mid-task in any enterprise app.
Re-authenticating in a second tab preserved the unsaved canvas. It is a small thing, but it is the same failure mode I have walked end users through more times than I can count: work does not disappear, the session backing it does, and the fix is almost always to re-establish trust, not start over.
Why this is IAM work, not SEO work
Take away the sitemap and the scoring rubric and what is left is OAuth2 client lifecycle management, credential rotation without downtime, authentication-versus-authorization debugging, incremental least-privilege scoping with re-consent, and session expiry handling across a multi-credential system. That is the job. The SEO report was just the excuse to do it hands-on instead of reading about it.
If you are building anything that touches more than one API, budget real time for the identity plumbing. It will take longer than the feature you actually wanted, and it will teach you more.
Some technical details (internal workflow IDs, specific credential names) have been simplified or omitted.