👋 Hi there, this article explains how to create a custom OmniAuth strategy and then load it using Devise. This allows you to easily develop your strategy without having to create a gem.
If you don't know what Devise or OmniAuth are, I guess you can stop here. I am glad you read that far already.
At the end of the article there's also a call for help because I am a new Rails developer and I can't figure everything out!
Backstory
I am building a product and I want users to be able to sign in with Slack.
Since I am using Devise I found out that Devise can work with OmniAuth to allow sign in from various OAuth providers like Facebook, Twitter: those are called OmniAuth strategies.
I found the Slack strategy on GitHub: omniauth-slack. But after doing a due diligence I discovered three things:
- The Slack strategy is not official and not maintained: last commit April 6 2017, lots of issues and pending PRs
- The Slack strategy forks are not maintained or filled with a lot of complex features I don't need
- The actual code required to make a custom Slack strategy is 40 lines of Ruby. Which might explain why it's not maintained.
What do I do? NIH syndrome to the rescue! I am gonna write another Slack OmniAuth strategy. To be honest, since I am learning Ruby and Rails I was mostly curious how Devise and OmniAuth were really working together.
Creating a custom OmniAuth strategy
The OmniAuth documentation provides information on how to create a new strategy but it does not tells you how to load such code. Most people will create reusable gems and then add that to their Gemfile.
Since I was already far away from my initial need (Allow people to sign in with Slack), I did not wanted to now dig into how to create gems and develop them locally, that would be for another time, maybe.
All I wanted to do was for this line:
config/initializers/devise.rb
config.omniauth :slack, ENV['SLACK_CLIENT_ID'], ENV['SLACK_CLIENT_SECRET'],
scope: 'identity.basic,identity.email,identity.team,identity.avatar'
To load my own Slack OmniAuth strategy.
Now the first question I asked myself is: where do I put my custom strategy so that Rails and devise can access it?
Loading custom code in Rails
As a new Rails developer not used to autoloading, auto reloading and conventions. It was not very clear to me where to put my code but eventually I settled on lib/omniauth/slack.rb.
From the outside, it seems the Rails community is confused about the question of adding custom code and loading/autoloading it.
This directory is not part of the autoloading mechanism so you have to put require 'omniauth/slack'
at the top of the file config/initializers/devise.rb. The whole OmniAuth and Devise ways of detecting strategies and loading them are completely blurry to me. If you know a better way, let me know.
Knowing 1. where to put my strategy and 2. how to load it were the big challenges for me.
Now onto the actual Slack strategy implementation.
The Slack strategy
Here's the actual code that will allow you to add a sign-in with Slack feature on your Rails application:
lib/omniauth/slack.rb
require 'omniauth-oauth2'
module OmniAuth
module Strategies
class Slack < OmniAuth::Strategies::OAuth2
option :client_options,
site: 'https://slack.com',
token_url: 'api/oauth.access'
uid do
"#{raw_info.dig('user', 'id')}-#{raw_info.dig('team', 'id')}"
end
info do
{
name: raw_info.dig('user', 'name'),
email: raw_info.dig('user', 'email')
}
end
extra do
{ raw_info: raw_info }
end
def raw_info
@raw_info ||= access_token.get('/api/users.identity').parsed
end
def callback_url
full_host + script_name + callback_path
end
end
end
end
Further considerations
This is the part where I need your help now...
Ideally I would like to avoid the line require omniauth/slack
at the top of config/initializers/devise.rb but I have no idea on how to do that.
I also have to reload my Rails application whenever I make a change to the strategy. I suspect this is because of Devise or OmniAuth doing something fancy here. I tried to create a separate class and adding the lib
folder to the autoloading mechanism and the auto reloading of this dummy class was working. But somehow not in the case of an OmniAuth strategy.
Finally, inside lib/omniauth/slack.rb I have to use either raw_info['team']['name'] or
raw_info.dig('team', 'name')while inside the Rails controller I can access
request.env['omniauth.auth'].extra.raw_info.team.name` and I have no idea why or where is this magic documented.
🔚 That's it!
As always, do note that this post is from someone learning Rails. If you read something obviously wrong and/or would like to add to this article then do it in the comments and I will be very happy to update my text.
If you're trying to implement a Slack sign-in feature on your application and you're struggling, also jump in the comments and let me know.
If you have your own point of view on Devise and OmniAuth I would be glad to hear it too.
Thanks for reading, if you enjoyed this post, share it for others to discover it:
Top comments (7)
you can simply move your code into a gem, for instance have a look at the google omniauth gem: github.com/zquestz/omniauth-google... which is basically the same as your script, with very little boilerplate around it.
Then you can just add that gem to your Gemfile. You can keep the gem's code inside your app, and just add the gem with a
path
option pointing to the sub-folder which contains your gem. Auto-reloading should work for the code inside your gem.Add a nice README and some specs, and you can even publish your gem as open source 🎉 (also giving you bragging rights about writing an entire library yourself 😎)
UPDATE: just checked for fun, such gem already exists 🤷 github.com/kmrshntr/omniauth-slack...
Hey there thanks for the comment, it will serve others for sure. As for me:
Happy coding!
Hi Vicent.
I'm facing the same problem you had. I want my application can login with Devise and Google and Slack. Your post really helped me because I could resolve the issues with gems.
But now, what is the endpoint I have to configure on my Slack app?
localhost:5000/auth/slack/callback, I have this one, but I seems that does not work.
Thanks.
Hi @vvo , am also a beginner in rails, thanks for your effort for really creating a local OmniAuth strategy for slack. By the way slack is now using oauthv2 so the above solution is using the legacy login or the new login architecture ?
Hey there, probably this is outdated indeed so you may want to dig into new ways to achieve this. Good luck!
for your question about
require
are you using Rails 6 ?Rails 6 comes with github.com/fxn/zeitwerk by default which should auto require (as well as hot reload)
Wondering if this could help?
Hey Adrien! I was able to verify that yes require worked for a file and auto reloading worked. If you put it in the app folder though. Inside the lib folder you first need to add the lib folder to the load paths.
Once you do that, from any controller I was able to verify that my custom code was automatically loaded (class was available, without a call to require) and reloaded on change.
But, as for Devise and OmniAuth, it would not detect my strategy by default. Thus I had to add a require call at the top of config/initializers/devise.rb and even doing so it would never reload. I believe Devise and OmniAuth are doing some weird magic to auto detect the strategy and also maybe cache it no matter what...