GitHub OAuth Authentication with Decap CMS: Troubleshooting Journey
by Chung Kang, Member

Initial Problem
Our Decap CMS was failing to authenticate with GitHub OAuth in production. The authentication popup would open briefly and then immediately close without completing the authentication process. However, it worked correctly in our local environment.
What We Tried and What Went Wrong
1. String Template Issue
We first suspected a string template issue in our callback code. The original code used single quotes, which don't allow variable interpolation:
window.opener.postMessage(
'authorization:github:success:${JSON.stringify(content)}',
"*"
);
We changed this to use backticks for proper interpolation:
window.opener.postMessage(
`authorization:github:success:${JSON.stringify(content)}`,
"*"
);
However, this wasn't the primary issue.
2. Domain Inconsistency
We noticed that our config had domain inconsistencies:
- site_domain: jacobsonlawrence.com
- base_url: https://www.jacobsonlawrence.com
This could cause issues with message origins. We aligned all domains to be consistent, but this alone didn't solve the problem.
3. Message Reception vs Processing
We added event listeners to confirm that the message was being properly received. The logs showed:
- The message was successfully sent from the popup
- The message was successfully received by the parent window
- However, Decap CMS wasn't acting on the received message
This indicated that while communication was working, the message wasn't in the exact format that Decap CMS expected.
4. Enhanced Debugging
We created a debug version of the callback that would:
- Stay open rather than automatically closing
- Display detailed information about the token
- Allow manual testing of different message formats
- Show window.opener status
This confirmed that window.opener existed and messages were being sent correctly.
5. Multiple Message Formats
We tried several different message formats:
- Standard:
authorization:github:success:{...}
- Netlify CMS:
netlify-cms-oauth-provider:{...}
- Raw JSON: Just the content object
None of these resolved the issue by themselves.
The Solution: Two-Way Communication Pattern
The breakthrough came when we examined a template that worked locally. The key difference was a two-way communication pattern:
- Initiating Contact: The popup first sends a message to the parent window saying "I'm starting to authorize":
window.opener.postMessage("authorizing:github", "*");
- Waiting for Acknowledgment: The popup sets up a listener to receive a response from the parent:
window.addEventListener("message", receiveMessage, false);
- Responding with Token: Only after receiving a response from the parent window does the popup send the actual token:
const receiveMessage = (message) => {
window.opener.postMessage(
`authorization:github:success:${JSON.stringify(content)}`,
message.origin
);
};
This pattern ensures that:
- The parent window (Decap CMS) is ready to receive authentication data
- The origin is properly established for secure cross-window communication
- The message is sent at the right time in the expected sequence
Why This Works
Decap CMS is based on Netlify CMS, which implements a specific OAuth flow:
- When you click "Login with GitHub," Decap CMS expects to initiate the process
- The popup should notify Decap CMS that it's starting authorization
- Decap CMS acknowledges and prepares to receive the token
- The popup completes authentication and sends the token in the expected format
Without this handshake, Decap CMS might receive the token but not recognize it as part of an authentication flow it initiated.
Additional Factors
-
Security Considerations: Using
message.origin
instead of "*" for the target origin ensures the message is only received by the intended recipient. -
Timing Issues: The template includes proper timing for message exchange, ensuring all parties are ready before proceeding.
-
Complete HTML Document: The working solution returns a complete HTML document from the callback, not just a script snippet, which might affect how browsers process it.
Learning Points
-
OAuth Flows are Specific: OAuth implementations often have specific patterns that must be followed exactly.
-
Two-Way Communication: For secure cross-window communication, a proper handshake pattern is often required.
-
Origin Matters: In production environments, message origins must match exactly for security reasons.
-
Debug Tools are Essential: Adding proper logging and debug interfaces was crucial to identifying where the process was breaking down.
-
Working Examples Matter: When troubleshooting complex interactions, examining a working example can reveal subtle but critical implementation details.
The key insight was that Decap CMS expected a specific sequence of messages for authentication, not just a single message with the token. This pattern ensures security and proper integration with the CMS.