Adding 'Sign in with Apple' to your Ruby on Rails 7.1 App: A Step-by-Step Guide
Also, a fix for the nasty CSRF issue.

I love building systems that scale effortlessly and stand the test of time. Over the past 20+ years, I’ve worked across iGaming, PropTech, Healthcare, and Security, leading teams and writing code that keeps products fast, secure, and reliable.
I’ve helped engineering organizations grow stronger and more resilient, building systems that proved their value all the way through acquisition.
My favorite projects are the ones that make complex systems simpler — the kind that turn chaos into clarity and help teams move faster with confidence.
I added omniauth-apple to one of my side projects to get 'Sign in with Apple' authentication. I hit a couple of issues that took me a while to fix, so I want to share how I solved them, hoping it saves others some time.
Getting Started
I'm working with Rails 7.1 and authentication-zero. My Gemfile already has omniauth and omniauth-rails_csrf_protection because I passed the --omniauthable flag to the authentication-zero generator:
rails generate authentication --omniauthable
Next, I added omniauth-apple to my Gemfile:
gem "omniauth-apple"
Configuring 'Sign in with Apple' requires several steps on the Apple side of the house that I won't go into. However, the short version is you will need an Apple Developer account, and you'll need to add some configuration to it. The omniauth-apple gem outlines how to do that in its README.
Next, I added the Apple configuration to my Custom Credentials:
apple:
bundle_id: com.example
app_id_prefix: <redacted>
key_id: <redacted>
private_key: |
-----BEGIN PRIVATE KEY-----
<redacted>
-----END PRIVATE KEY-----
Then, I added the apple provider to config/initializers/omniauth.rb:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :apple,
Rails.application.credentials.apple[:bundle_id],
"",
{
scope: "email name",
team_id: Rails.application.credentials.apple[:app_id_prefix],
key_id: Rails.application.credentials.apple[:key_id],
pem: Rails.application.credentials.apple[:private_key]
}
end
Finally, you must add a button to 'Sign in with Apple' on your login page. I'm using tailwindcss via tailwindcss-rails to style the button:
<%= button_to "/auth/apple", "data-turbo" => false, class: "text-white bg-[#050708] hover:bg-[#050708]/90 focus:ring-4 focus:ring-[#050708]/50 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-[#050708]/50 dark:hover:bg-[#050708]/30 mr-2 mb-2" do %>
<svg class="mr-2 -ml-1 w-5 h-5" aria-hidden="true" focusable="false" data-prefix="fab" data-icon="apple" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"></path></svg>
<span class="text-sm font-semibold leading-6">Sign in with Apple</span>
<% end %>
If you want to test this locally, you'll need to use https. There are multiple options for that, but I use ngrok. Once you have an https URL pointing at your local Rails server, you'll need to add a couple of things to your config/environments/development.rb:
config.force_ssl = true
config.hosts << "yourexternalurl.com" # If you're using something like ngrok
While we're at it, you might want to turn on force_ssl in your config/environments/production.rb for your deployed app:
config.assume_ssl = true # You may need this depending on your prodution deployment
config.force_ssl = true
Note: Don't forget to update your domains and redirect_uris in your 'Sign in with Apple' configuration within your Apple Developer account (see the README)
The force_ssl configuration is crucial because it ensures that your Rails session cookie is set with the Secure attribute. We're going to set SameSite to None later to fix an error, which only works with a secure cookie.
That's a fair bit of work! You are probably excited to try this out, but you'll likely encounter an error.
CSRF detected 😱
If you click on your 'Sign in with Apple' button, everything should work fine until you're redirected back. If you're testing locally, you'll probably see this:

This took me quite a while to wrap my head around, and I'll explain what's going on, but if you want the fix and don't care about the explanation, add this to your config/application.rb (credit to jmonteiro)
config.action_dispatch.cookies_same_site_protection =
lambda do |request|
request.path.starts_with?("/auth/apple") ? :none : :lax
end
Why does this happen?
There are multiple discussions about what's happening here and even a couple of PRs against omniauth-apple that have been closed by the maintainer. A lot is going on, but I'll try to summarize it in a Rails context and provide links for more information along the way:
Modern versions of Rails set the
SameSiteattribute toLaxon the session cookie (see the PR here for more explanation). This means the cookie is sent along to cross-site requests (like our 'Sign in with Apple' link), but it only works with "safe" HTTP methods likeGET.Apple somewhat breaks OAuth protocol by redirecting back with a
POSTinstead of aGET.Part of a secure OAuth handshake is generating a
state, storing it, and then sending it along as a parameter to the identity provider (in this case, Apple). When the user is redirected back, the identity provider sends thatstateback as a parameter for the server to compare to its storedstate. If it doesn't match, we have a CSRF problem (see more here).omniauth-oauth2 stores the state in the rack session, which, in Rails, means storing it in a cookie (see the first bullet).
Because of the
POSTredirect back from Apple and theLaxSameSitecookie setting, thestateparameter never gets back to our Rails app, andomniauth-oauth2correctly raisescsrf_detected.
Now, back to the fix (see above). In our case, we just set SameSite to None, but only in the case where we're hitting the omniauth /auth/apple route.
Is this secure? Well, you'll have to make that determination for yourself, but let me explain why I'm ok with it:
We're selectively setting
SameSitetoNonein the case where we're making a request to 'Sign in with Apple' (viaomniauth). That means, on all our other routes, the session cookie is set withSameSitetoLax.We already have another form of CSRF protection built into OAuth with the state parameter.
Rails cookie sessions are encrypted, meaning if there was a man-in-the-middle attack, the attacker couldn't read the
state, which means they couldn't send it back in the redirect andomniauth-oauth2would raisecsrf_detected.
I hope that helps! If you feel like I got something wrong or you have a question, please comment or shoot me an email at [email protected]
