Skip to content

Support session token authentication for direct uploads#2706

Merged
flavorjones merged 4 commits into
basecamp:mainfrom
rbarbosa:pr/direct-uploads-session-auth
Jun 10, 2026
Merged

Support session token authentication for direct uploads#2706
flavorjones merged 4 commits into
basecamp:mainfrom
rbarbosa:pr/direct-uploads-session-auth

Conversation

@rbarbosa

@rbarbosa rbarbosa commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Context

I'm building a native iOS client that uses magic link (session token) authentication. Image and file uploads are core functionality, but since the direct upload endpoint only bypasses CSRF for bearer token auth, session-based clients get 422 errors. This has been a real blocker for me.

Summary

  • The Active Storage direct upload endpoint only skips CSRF protection for bearer token auth, causing 422 errors for native mobile apps using magic link (session-based) authentication
  • Broadens the skip_forgery_protection condition to also bypass CSRF for authenticated JSON requests
  • Adds test coverage for session-token direct uploads and cross-account authorization

Security considerations

This is safe because:

  • Still requires valid authentication (session or bearer token)
  • Only applies to JSON requests (web UI remains CSRF-protected)
  • CSRF attacks target browser ambient authority — mobile JSON API requests aren't vulnerable

Test plan

  • Existing bearer token tests pass
  • New test: session token + JSON → 201 success
  • New test: session token + wrong account → 403 forbidden

@flavorjones

Copy link
Copy Markdown
Member

The security check can be ignored -- will go away if this branch is rebased onto main.

@rbarbosa rbarbosa force-pushed the pr/direct-uploads-session-auth branch from d67b43b to a070641 Compare March 18, 2026 21:43
Copilot AI review requested due to automatic review settings March 18, 2026 21:43
@rbarbosa

Copy link
Copy Markdown
Contributor Author

Thanks @flavorjones! Let me know if anything else is needed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates ActiveStorage direct upload CSRF behavior so native/mobile clients using session-token (magic link) authentication can successfully create direct uploads via JSON, and adds integration tests to cover this flow and cross-account authorization.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Changes:

  • Broaden skip_forgery_protection for ActiveStorage::DirectUploadsController to include authenticated JSON requests (in addition to bearer token auth).
  • Add direct-upload tests for session-token JSON requests and cross-account access.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
config/initializers/active_storage.rb Expands the CSRF-skip condition for direct uploads to support session-authenticated JSON clients.
test/controllers/active_storage/direct_uploads_controller_test.rb Adds integration tests for session-token direct upload success and forbidden cross-account behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/controllers/active_storage/direct_uploads_controller_test.rb Outdated
Comment thread config/initializers/active_storage.rb Outdated

@robzolkos robzolkos left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be missing something, but I think this is very close and mostly needs a CSRF-enabled test to show the session-auth path is really covered.

The bit I’m unsure about is that this skip_forgery_protection condition depends on authenticated?, and I think that only gets established during require_authentication. My read is that the Active Storage forgery callback runs before that, so I’m not yet convinced a session-backed JSON direct upload will skip CSRF in practice.

Could you please add a test variant with forgery protection explicitly enabled? There’s a good example of the setup/teardown pattern in test/controllers/concerns/request_forgery_protection_test.rb, and the existing sign_in_as helper lives in test/test_helpers/session_test_helper.rb. If that passes for the signed-in session upload flow, this would increase confidence that this works as intended.

@rbarbosa

Copy link
Copy Markdown
Contributor Author

I may be missing something, but I think this is very close and mostly needs a CSRF-enabled test to show the session-auth path is really covered.

The bit I’m unsure about is that this skip_forgery_protection condition depends on authenticated?, and I think that only gets established during require_authentication. My read is that the Active Storage forgery callback runs before that, so I’m not yet convinced a session-backed JSON direct upload will skip CSRF in practice.

Could you please add a test variant with forgery protection explicitly enabled? There’s a good example of the setup/teardown pattern in test/controllers/concerns/request_forgery_protection_test.rb, and the existing sign_in_as helper lives in test/test_helpers/session_test_helper.rb. If that passes for the signed-in session upload flow, this would increase confidence that this works as intended.

Hey @robzolkos, thank you for taking the time to review and pointing me in the right direction. Let me know if there are any other cases or concerns not yet covered with the update I've pushed.

@robzolkos robzolkos left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rbarbosa looks good! Thank you. cc @flavorjones

@rbarbosa

rbarbosa commented Apr 4, 2026

Copy link
Copy Markdown
Contributor Author

Hey @flavorjones, just checking in — any concerns on your end? Happy to address anything if needed.

Copilot AI review requested due to automatic review settings May 31, 2026 14:48
@flavorjones flavorjones force-pushed the pr/direct-uploads-session-auth branch from afbacca to 9af8b50 Compare May 31, 2026 14:48
@flavorjones

Copy link
Copy Markdown
Member

Rebased onto master to try to get CI green.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread config/initializers/active_storage.rb Outdated
Comment thread test/controllers/active_storage/direct_uploads_controller_test.rb Outdated
@flavorjones

flavorjones commented May 31, 2026

Copy link
Copy Markdown
Member

@rbarbosa @robzolkos I added a couple of commits to:

  1. add a negative auth test on the active storage controller
  2. use the existing RequestForgeryProtection concern instead of skip_forgery_protection

I'm mostly interested in whether you agree (2) is an improvement. It switches the controller to use the :header_only (Sec-Fetch-Site) CSRF strategy already used by ApplicationController.

rbarbosa and others added 4 commits May 31, 2026 11:24
The Active Storage direct upload endpoint only skips CSRF protection
for bearer token authentication. Native mobile apps using magic link
(session-based) authentication get 422 errors when uploading files,
because they don't send CSRF tokens for JSON API requests.

Broaden the skip_forgery_protection condition to also bypass CSRF for
authenticated JSON requests, which is safe since the session token is
already validated and CSRF attacks don't apply to non-browser API
clients.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use resume_session instead of authenticated? in the skip_forgery_protection
condition so the session is established before the forgery check runs,
regardless of callback ordering.

Add a test with forgery protection explicitly enabled to prove the CSRF
bypass works in production, not just in test mode where CSRF is disabled
by default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 31, 2026 15:24
@flavorjones flavorjones force-pushed the pr/direct-uploads-session-auth branch from 8e98965 to 187870d Compare May 31, 2026 15:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread test/controllers/active_storage/direct_uploads_controller_test.rb
@rbarbosa

rbarbosa commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Thank you both for taking the time to review and improve this 🙏 .
I agree — using the existing RequestForgeryProtection concern is a nice improvement: the end result is cleaner and more consistent with the rest of the app.

@flavorjones flavorjones merged commit df294cd into basecamp:main Jun 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants