A scheduled job and a person at a login prompt call the same API with different credentials, different lifetimes and different owners. The job needs a network path, an application identity and a permission set; the person needs a login, a second factor and a session. Mixing the two produces automation that breaks whenever the person's password changes, or a multi-factor policy weakened just enough to keep a cron job alive.

Three gates that look like one

A source-address rule asks where the request came from. Authentication asks who is calling and which proofs were presented. Authorization asks whether this principal may perform this operation. A VPN changes only the first of the three, and it changes it for every process on the host rather than for the job alone, so keep the three observations separate in the incident record.

That is why changing the exit node can fix a source-address rejection and leave an invalid token untouched. It is also why a valid API token does not make a private endpoint reachable from a job with no route to it.

The interactive path is a poor automation template

Scripting a person's login is tempting because it works once. The script stores a password, maybe a one-time-password seed, and then depends on a challenge designed to require a human. The OAuth security best current practice, RFC 9700, prohibits the resource-owner-password-credentials grant and explains why it fits two-factor and multi-step authentication badly.

The supported alternative is usually a confidential-client flow in which the authorization server authenticates the application and issues a token. The client-credentials grant in RFC 6749 section 4.4 is one example. Its existence does not mean every API accepts it, or that a user-delegated task can be replaced by it, because those are questions for the service owner. Ask which unattended model the service supports before writing any code.

Where the VPN still belongs

A VPN can supply the network path a private API requires, and that is a legitimate role. It does not authenticate the job to the application, and it does not scope what the job may do. A shared egress address also identifies the host rather than the job, so an allowlist entry for that address admits every process running on it.

If the job runs on a shared runner, that distinction matters more rather than less. Another workflow on the same host inherits the route and the address, and the API cannot tell the two apart. That is the same weakness as a shared password, moved one layer down. Put the restriction where the application can enforce it, and isolate the runner if you cannot scope the allowlist more tightly.

Compare the two designs honestly

A human login gives you a real person, a real second factor and an auditable session, and it cannot run unattended. An application identity gives you unattended execution, a reviewable scope and a credential you can rotate, and it cannot act for a user who has not delegated anything. Neither is a weaker version of the other; they answer different questions. Choose by asking whether the task must run with nobody present, and whether the operation must be attributed to a specific user.

If both answers are yes, the design needs a delegation model rather than a borrowed password. Write that decision down where the next maintainer will find it.

What to verify before handover

Run one permitted operation and one required denial from the actual worker, and keep both results. Then test an expired credential and the behavior when the network path is unavailable, because those are the two failures that wake someone at night.

curl -sS -o /dev/null -w '%{http_code}\n' \
  -H "Authorization: Bearer TOKEN" https://api.example.com/v1/records

Record the principal, the scope, the rotation owner and the execution environment next to that result. That record is what makes the next rotation routine rather than an incident. It is also the part an auditor asks for.

Sɛbɛn minnu bɛ tali kɛ