DEV Community

sectasy
sectasy

Posted on

How to Implement Login with Discord feature in Rails 7 using Devise

Discord is a widely used communication platform among gamers and communities around the world. Integrating the Discord login feature into your Ruby on Rails application can provide a more streamlined and secure way for users to sign up and log in.

Step 1: Set up the Discord Application

To integrate with Discord OAuth, you first need to set up a Discord Application. To do this, follow these steps:

  1. Log in to the Discord Developer Portal (https://discord.com/developers/applications)
  2. Click on the New Application button
  3. Give your application a name and click on the Create button
  4. From the OAuth2 section, copy CLIENT ID and CLIENT SECRET
  5. Paste your redirection URL, typically it will be https://<your-domain>/user/auth/discord/callback

NOTE: For the integration to work correctly, the server must be running with secure HTTPS, which is best done on a VPS using a domain. If you don't have a VPS, you can get one for free from Oracle, and a free domain from https://free-for.dev/#/?id=domain.

Step 2: Add the required Gems to your Gemfile

gem 'devise', '~> 4.8.1' gem 'omniauth', '~> 1.3.1' gem 'omniauth-discord', '~> 1.0.0' 
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure the omniauth-discord gem

Enter the saved data from Step 1 into your credentials.yml file

EDITOR=vim rails credentials:edit 
Enter fullscreen mode Exit fullscreen mode
discord: client_id: 1068564434567456784 secret: BCf6heHXdcCc5Lwy5YL5R5BJ6SDm6QK2 
Enter fullscreen mode Exit fullscreen mode

Next, set the appropriate configuration for the Discord provider in the config/initializers/devise.rb file by adding the following two lines:

discord_oauth = Rails.application.credentials.discord config.omniauth :discord, discord_oauth[:client_id], discord_oauth[:secret], scope: 'email identify' 
Enter fullscreen mode Exit fullscreen mode

Step 4: Add a method that creates a user with omniauth to the model.

def self.from_omniauth(auth) where(provider: auth.provider, uid: auth.uid).first_or_create do |user| user.provider = auth.provider user.uid = auth.uid user.username = auth.info.name user.email = auth.info.email user.password = Devise.friendly_token[0, 20] end end 
Enter fullscreen mode Exit fullscreen mode

If you want to download user profile pictures from discord, it's best if you use the carrierwave gem

gem 'carrierwave', '~> 2.2.3' gem 'carrierwave-i18n', '~> 0.2.0' 
Enter fullscreen mode Exit fullscreen mode

And create a propier uploader app/uploaders/avatar_uploader.rb

class AvatarUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick storage :file process :resize_to_fit => [ 150, 150 ] def store_dir "uploads/#{model.class.to_s.underscore}" end def size_range 0..1.megabytes end def extension_allowlist %w(jpg jpeg png) end def filename "#{model.id}.#{file&.extension}" if file end end 
Enter fullscreen mode Exit fullscreen mode

Next, mount uploader to your model.

mount_uploader :avatar, AvatarUploader 
Enter fullscreen mode Exit fullscreen mode

NOTE: Gem carrierwave uses the MiniMagick library to work with photos. Don't forget to install this library on your system (sudo apt-get install libmagickwand-dev).

After that, let's go back to our self.from_omniauth method for a moment to add support for downloading avatars.

def self.from_omniauth(auth) image_available = Faraday.get(auth.info.image).status == 200 where(provider: auth.provider, uid: auth.uid).first_or_create do |user| user.provider = auth.provider user.uid = auth.uid user.username = auth.info.name user.email = auth.info.email user.password = Devise.friendly_token[0, 20] user.remote_image_url = auth.info.image if image_available end end 
Enter fullscreen mode Exit fullscreen mode
image_available = Faraday.get(auth.info.image).status == 200 
Enter fullscreen mode Exit fullscreen mode

if the user does not have an avatar on discord, then under auth.info.image we will get the value nil (similarly in the case if for some reason the avatar on cdn discord is unavailable) and our uploader will return an error, to prevent this you need to first check if the avatar is correct.

account.remote_image_url = auth.info.image if image_available 
Enter fullscreen mode Exit fullscreen mode

The remote_image_url method comes from our uploader. This method is to download the avatar from the Discord CDN and save it locally on the user.

Step 5: Implement the Omniauth Callback Controller

To handle the login from the Discord provider, you need to implement a callback controller. To do this, create a new file in your application's app/controllers directory named omniauth_callbacks_controller.rb with the following code:

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController skip_before_action :authenticate_account! def discord @user = User.from_omniauth(request.env["omniauth.auth"]) sign_in(:account, @user) # in my case response was incorrect, so I made it by myself. redirect_to after_sign_in_path_for(@user), notice: t('devise.omniauth_callbacks.success', kind: @user.provider) end end 
Enter fullscreen mode Exit fullscreen mode

Step 6: Add a discord login button to view

Now that you have completed the setup and configuration, it's time to implement the "Login with Discord" button in your application. You can do this by adding the following code to your view file:

= button_to("Log in with Discord", user_discord_omniauth_authorize_path) 
Enter fullscreen mode Exit fullscreen mode

Step 7: Test the integration

Finally, test the integration by running your Rails application and clicking on the "Login with Discord" button. If everything has been set up correctly, you should be redirected to Discord for authentication and then back to your application where you will be logged in as the corresponding user.

And you can optionally write a tests for that:

class Users::OmniauthCallbacksControllerTest < ActionController::TestCase include Devise::Test::ControllerHelpers setup do @request.env["devise.mapping"] = Devise.mappings[:account] @request.env["omniauth.auth"] = discord_oauth2_mock OmniAuth.config.test_mode = true end teardown do OmniAuth.config.test_mode = false end test 'authorize user from discord' do assert_not @controller.current_user post :discord, params: { code: 'H7buenBzdnlKavjCdG6TWzNLgjrF5p' } assert_redirected_to <your_app_main_path> assert_not_nil @controller.current_user assert @controller.current_user.provider == 'discord' end ... more cases below private def discord_oauth2_mock OmniAuth.config.mock_auth[:discord] = OmniAuth::AuthHash.new({ provider: 'discord', uid: '940987618345254912', info: { email: 'fakeemail@gmail-fake.com', username: "David", image: 'https://cdn.discordapp.com/avatars/940987618345254912/c62c9b46dc9e877fff993bbe56ee7452' }, credentials: { token: 'abcdefgh12345', refresh_token: '12345abcdefgh', expires_at: DateTime.now } }) end end 
Enter fullscreen mode Exit fullscreen mode

Integrating Discord OAuth into your Ruby on Rails application can provide a more seamless and secure way for users to sign up and log in. By following the steps outlined in this tutorial, you can have your own "Login with Discord" button up and running in no time.

Top comments (2)

Collapse
 
to_articles profile image
Articles to read online
Collapse
 
sectasy0 profile image
sectasy

What do you mean?