DEV Community

Cover image for Devise: create a local OmniAuth strategy for Slack
Vincent Voyer
Vincent Voyer

Posted on

Devise: create a local OmniAuth strategy for Slack

_Photo by [Adeolu Eletu](https://unsplash.com/@adeolueletu?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)_

πŸ‘‹ 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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Top comments (7)

Collapse
 
swiknaba profile image
Lud • Edited

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

Collapse
 
vvo profile image
Vincent Voyer

Hey there thanks for the comment, it will serve others for sure. As for me:

  • I am no more doing Rails so I won't publish such a gem, but again I think your comment is valuable because it shows others how to do it and how to have it working locally
  • As for the official omniauth slack strategy, I tried it and got issues as explained in the blog post (dev.to/vvo/devise-create-a-local-o...), last update was in 2017

Happy coding!

Collapse
 
jaimealvarez1984 profile image
Jaime Alvarez AcuΓ±a

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.

Collapse
 
iamsatya profile image
Satya Swaroop Mohapatra

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 ?

Collapse
 
vvo profile image
Vincent Voyer

Hey there, probably this is outdated indeed so you may want to dig into new ways to achieve this. Good luck!

Collapse
 
adrienpoly profile image
Adrien Poly

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?

Collapse
 
vvo profile image
Vincent Voyer

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