loading...
Cover image for Devise: create a local OmniAuth strategy for Slack

Devise: create a local OmniAuth strategy for Slack

vvo profile image Vincent Voyer ・4 min read
Photo by Adeolu Eletu

👋 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:

  1. The Slack strategy is not official and not maintained: last commit April 6 2017, lots of issues and pending PRs
  2. The Slack strategy forks are not maintained or filled with a lot of complex features I don't need
  3. 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'] orraw_info.dig('team', 'name')while inside the Rails controller I can accessrequest.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:

This is a cat GIF, I love cat GIFs

Posted on Dec 9 '19 by:

vvo profile

Vincent Voyer

@vvo

🌱 Growing indie hacker. Previously: JavaScript APIs @ algolia.com

Discussion

markdown guide
 

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.

 

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...