How to Implement Login with Discord feature in Rails 7 using Devise
sectasy
Posted on February 5, 2023
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:
- Log in to the Discord Developer Portal (https://discord.com/developers/applications)
- Click on the
New Application
button - Give your application a name and click on the
Create
button - From the
OAuth2
section, copyCLIENT ID
andCLIENT SECRET
- 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'
Step 3: Configure the omniauth-discord
gem
Enter the saved data from Step 1 into your credentials.yml
file
EDITOR=vim rails credentials:edit
discord:
client_id: 1068564434567456784
secret: BCf6heHXdcCc5Lwy5YL5R5BJ6SDm6QK2
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'
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
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'
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
Next, mount uploader to your model.
mount_uploader :avatar, AvatarUploader
NOTE: Gem
carrierwave
uses theMiniMagick
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
image_available = Faraday.get(auth.info.image).status == 200
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
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
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)
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
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.
Posted on February 5, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.