This document was written for the ginjo-omniauth-slack ruby gem. It attempts to clarify the OAuth2 authorization cycle and how that cycle is implemented in your appliation with the ginjo-omniauth-slack gem.
The OAuth2 cycle is a three-way dance between the user's browser, the OAuth2 provider (Slack API), and the application server (your Slack App). It should work this way for any OAuth2 provider, including Slack.
-
The user/browser makes a request to
https://slack.com/oauth/authorize
, passing the application's client-id, requested-scopes, and optionally state, team-id, and redirect-uri. Slack then runs the user through the authorization dialogs. -
Upon successful authorization, Slack redirects the browser to the application's callback url (or redirect_uri in Slack's terms) with a short-lived authorization code, for example:
https://yourapp.com/auth/slack/callback?code=ABCDE87364
-
Omniauth-slack intercepts this request and exchanges the code, via Slack API, for a valid access-token.
And that's it. Control is then given to your application's /auth/slack/callback
action.
The next step would be the application storing the access-token, maybe making additional API requests, and then rendering a page to the browser or redirecting to another action. In a working app, a session would store a reference to the token, and the token would be stored in a database. Then for every request from that user, a valid access-token would be accessible and usable to make further API requests.
While the browser experience may appear simple, there's quite a lot happening behind the scenes. Omiauth-slack is running as part of the Rack stack, behind your main app, and it handles most of the above sequence before your application even receives the requests.
So lets run through the cycle again and take a closer look.
-
-
The user clicks a browser button which makes a request to your application at
https://yourapp.com/auth/slack
. This URI is likely the source or href of your signin-with-slack or add-to-slack button.Your application doesn't need to know about this endpoint, and it doesn't need to define an action for it. Omniauth-slack middleware recognizes this URI as the authorization request.
-
Omniauth-slack intercepts this request, considers your configuration, stores some data in a session variable, and then redirects the browser, with the necessary data embedded in the URI params, to Slack.
OmniAuth calls this the request phase, and your application sees none of it.
-
-
-
Having been redirected by omniauth-slack, the browser makes an authorization request to
https://slack.com/oauth/authorize
, passing the application's client-id, requested-scopes, and optionally state, team-id, and redirect-uri. This request contains everything Slack needs to authenticate the user and authorize access to Slack's API functions and data. -
Slack leads the user through any dialogs necessary to complete the authorization.
Depending on the setup, the requested (and awarded) scopes and permissions, and Slack's internal logic, this cycle could appear as a series of dialogs or as a simple request/response. If identity scopes were requested (signin-with-slack flow), and a team-id was passed in the params, and the given scopes were previously authorized, Slack may grant authorization without requiring the user to click on any dialogs at all.
Meanwhile, the application server and omniauth-slack are patiently waiting and have no idea what Slack, or the user, are doing at this point.
-
-
Upon successful authorization, Slack redirects the browser to the application's callback url (or redirect_uri in Slack's terms) with a short-lived authorization code, for example:
https://yourapp.com/auth/slack/callback?code=ABCDE87364
.Your application needs to define an endpoint (a route, an action, a method, etc.) for
/auth/slack/callback
, but omniauth-slack does all of its work before your app even sees the request. -
-
Omniauth-slack intercepts this request, and exchanges the authorization code for a valid access-token by making an API request to
https://slack.com/api/oauth.access
.The
oauth.access
response contains an access-token (and possibly other data) which omniauth-slack stores in the Rackenv
for later use by your application.OmniAuth refers to this part of the process as the callback phase, and you don't see any of it (Rack middleware magic).
-
Rack then passes this callback request to your app, and you are at the logical beginning of whatever action you defined for
/auth/slack/callback
.There is a lot of data available in the request
env['omniauth.auth']
andenv['omniauth.strategy']
. There are also other env variables defined by omniauth and omniauth-slack. See the gem docs for more about those.At this point, you will likely want to grab the
env['omniauth.auth']
hash and theenv['omniauth.strategy'].access_token
object. Use the access-token to make further API requests, or store the token and auth_hash for later retrieval.See the note about access tokens below.
-
The above cycle could be implemented in a Sinatra or Rails setup as simple as the ones described below, but first...
Before you try to implement omniauth-slack, create a Slack app on api.slack.com. Then setup the app's Redirect URL list in your Slack App's OAuth & Permissions
section on api.slack.com. Set one or more Redirect URL entries that match the domain:port of this simple application. The app doesn't have to be accessible from the public internet, just from your local machine, for example: http://localhost:9292
or http://192.168.0.5:8000
.
Create a Sinatra project directory, then add these files.
require 'omniauth-slack'
require 'sinatra'
require 'yaml'
enable :sessions
# optional
#set :port, '9292'
#set :bind, '0.0.0.0'
use OmniAuth::Builder do
provider :slack, SLACK_OAUTH_KEY, SLACK_OAUTH_SECRET, scope:'identity:read:user'
end
get '/auth/slack/callback' do
content_type 'text/yaml'
{ auth_hash: env['omniauth.auth'],
access_token: env['omniauth.strategy'].access_token
}.to_yaml
end
source 'https://rubygems.org'
gem 'ginjo-omniauth-slack' #, git:'https://github.com/ginjo/omniauth-slack'
gem 'sinatra'
gem 'puma'
Put those in their respective files, fill in your Slack OAuth2 credentials, then launch.
bundle install
bundle exec ruby super_simple.rb
Then point your browser to
http://<host-and-port-recognized-in-slack-redirect-uri-list>/auth/slack
When a successful authorization cycle completes, your browser should end up with a yaml representation of the auth_hash and access_token objects. What happens next is entirely up to your application.
Create a rails project, then add or modify these files. Note that this is not necessarily the best way to do this in a production system. It's just a demonstration of the bare necessities to get omniauth-slack working in Rails.
require 'omniauth-slack'
Rails.application.config.middleware.use OmniAuth::Builder do
provider :slack, SLACK_OAUTH_KEY, SLACK_OAUTH_SECRET, scope:'identity:read:user'
end
class AuthController < ApplicationController
def callback
render plain: { access_token: request.env['omniauth.strategy'].access_token.to_hash,
auth_hash: request.env['omniauth.auth']
}.to_yaml
end
end
get 'auth/slack/callback', to: 'auth#callback'
gem 'ginjo-omniauth-slack' #, git:'https://github.com/ginjo/omniauth-slack'
Don't forget to fill your Slack API credentials. Then start up Rails, and point your browser to
http://<host-and-port-recognized-in-slack-redirect-uri-list>/auth/slack
When a successful authorization cycle completes, your browser should end up with a yaml representation of the auth_hash and access_token objects. What happens next is entirely up to your application.
While Slack's access token is a simple string, the OAuth2 gem packages it, along with any other data returned from the /api/oauth.access
call, as an AccessToken instance. The OAuth2::AccessToken
instance is a useful and often overlooked tool in the OAuth2 gem. With a valid AccessToken instance (generated from every successful OAuth2 cycle), you have the full spectrum of Slack API functionality at your fingertips.
The AccessToken contains everything you need to make Slack API requests: The actual token string, the expiration data, the team, user, scope, and an OAuth2::Client instance with the API key and secret.
The AccessToken generated by omniauth-slack also has additional features, such as has_scope?(list-of-scopes)
, which queries the token's awarded scopes. This is handy for Slack Workspace apps and their multi-dimensional scopes.
Use the AccessToken#to_hash
method to prepare the token for serialization and storage in a database. This method strips off all unnecessary objects and leaves just the data.
When you want to reconstitute the access-token from a stored hash or string, use the OAuth2::AccessToken.from_hash
method. Or use omniauth-slack's convenience method:
access_token = OmniAuth::Slack.build_access_token(key, secret, access_token_string_or_hash)
Once you have a valid AccessToken instance, you can do things like
access_token.get('/api/apps.permissions.users.list')
access_token.refresh
access_token.post('/api/chat.postMessage', params: {channel: channel_id, text: message})
To extract data from the API response, call parsed
on the response object.
access_token.get('/api/channels.list').parsed['channels']
# => [{'id' => 1, 'name' => ...}, {'id' => ...}]