If you’ve deployed Strapi v5 on Heroku and tried integrating Google OAuth, you may have run into this dreaded error:
Error: Cannot send secure cookie over unencrypted connection
It typically appears right after hitting your Google login endpoint:
/api/connect/google
Even though your app is running over HTTPS, Strapi (via koa-session
) thinks the request is not secure, and refuses to set a Secure cookie. Here’s why it happens and how to fix it.
Why This Happens
When you deploy on Heroku:
- HTTPS traffic is terminated at Heroku’s load balancer.
- Your Strapi dyno receives the request as HTTP, unless you explicitly tell Koa to trust the proxy headers (
X-Forwarded-*
). koa-session
checksctx.secure
before setting Secure cookies.- Without proxy trust,
ctx.secure
isfalse
, so you get the error.
This is especially common with third-party OAuth flows (Google, GitHub, etc.) because they require Secure
+ SameSite=None
cookies to maintain the session across domains.
Step 1 — Configure config/server.ts
Tell Strapi to trust the proxy and set your public HTTPS URL:
export default ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
proxy: true, // trust X-Forwarded-* headers
url: env('URL', 'https://your-app-name.herokuapp.com'),
app: { keys: env.array('APP_KEYS') },
});
Step 2 — Update config/middlewares.ts
Move the session middleware before security/cors/body and enable proxy mode inside it:
export default [
'strapi::errors',
{
name: 'strapi::session',
config: {
key: 'strapi.sid',
secure: true, // required with SameSite=None
sameSite: 'none', // needed for Google OAuth cross-site cookies
rolling: false,
renew: false,
proxy: true, // tell koa-session to trust the proxy
},
},
'strapi::security',
'strapi::cors',
'strapi::body',
'strapi::logger',
'strapi::query',
'strapi::favicon',
'strapi::public',
];
Step 3 — Force Proxy Trust in src/index.ts
Some setups still fail because the proxy
setting in config/server.ts
doesn’t get applied early enough. You can enforce it at runtime:
// src/index.ts
import type { Core } from '@strapi/strapi';
export default {
register({ strapi }: { strapi: Core.Strapi }) {
// Ensure Koa trusts proxy headers from Heroku
strapi.server.app.proxy = true;
},
bootstrap() {},
};
Step 4 — (Optional) Debugging the Proxy Headers
If you want to verify what Heroku sends, add a quick middleware:
export default () => {
return async (ctx, next) => {
const xfp = ctx.get('x-forwarded-proto');
console.log('[PROTO DEBUG]', {
xfp,
secure: ctx.secure,
url: ctx.originalUrl,
});
await next();
};
};
When you hit your Google connect URL, you should see:
[PROTO DEBUG] { xfp: 'https', secure: true, url: '/api/connect/google' }
Step 5 — Environment Variables
On Heroku, make sure you have:
URL=https://your-app-name.herokuapp.com
Step 6 — Test the Flow
- Redeploy to Heroku.
- Open
https://your-app-name.herokuapp.com/api/connect/google
. - You should be redirected to Google, then back to
/api/connect/google/callback
without the 500 error.
Conclusion
The error isn’t a bug in Strapi — it’s a mismatch between Heroku’s proxying and Koa’s cookie security checks. By explicitly trusting the proxy in both Strapi and koa-session
, you allow ctx.secure
to be true
when traffic is HTTPS at the load balancer.
With this fix, Secure cookies will work correctly, and your Google OAuth integration will stop breaking.