Vincent Voyer
Posted on December 9, 2019
š 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:
Posted on December 9, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.