Configuring Omniauth for GitHub authentication is easy enough. But I needed to optionally add extra permissions to the authentication token. I eventually figured it out, but since I had to piece the steps together from various sources, I thought I’d document what I learned.
A basic Omniauth GitHub setup looks like this:
Rails.application.config.middleware.use OmniAuth::Builder do provider :github, github_id, github_secret end
That just gives you a basic read-only authorization, however. If you want to go beyond that, for instance to update user details and manage repositories, you have to pass a “scope” option. The value of the option must be a comma-delimited string of scope names.
Rails.application.config.middleware.use OmniAuth::Builder do provider :github, github_id, github_secret, scope: "user,repo" end
Available scopes include user
, public_repo
, repo
, and gist
.
That’s all you need if you always want the same set of priviledges. But what if you want to authenticate some users with more priviledges than others?
The answer lies in the Omniauth “setup phase”. You can pass a :setup
option to an Omniauth provider, with a proc which will be executed before the authentication is performed. Inside this proc you can query and alter the request environment. The Omniauth wiki has some basic documentation on how to do this.
I wanted to be able to pass an optional “role” parameter to the /auth/github
action, with an app-specific meaning which would be used to determine which scopes to request. In order to encapsulate this logic I specced-out a GithubAuthOptions
class:
describe GithubAuthOptions do subject { described_class.new(env) } context "given no special role" do let(:env) { Rack::MockRequest.env_for("/auth/github", params: {}) } its(:to_hash) { should be_empty } its(:to_hash) { should be_a(Hash) } end context "given a shopkeeper role" do let(:env) { Rack::MockRequest.env_for("/auth/github", params: {'role' => 'shopkeeper'}) } its(:to_hash) { should eq(:scope => 'repo') } end end
I wrote the following implementation:
class GithubAuthOptions def initialize(env) @request = Rack::Request.new(env) end def to_hash if 'shopkeeper' == @request.params['role'] {scope: 'repo'} else {} end end end
Finally, I added GithubAuthOptions
to my Omniauth initializer:
Rails.application.config.middleware.use OmniAuth::Builder do # ... provider :github, github_id, github_secret, setup: ->(env) { options = GithubAuthOptions.new(env) env['omniauth.strategy'].options.merge!(options.to_hash) } end
The net result: if I redirect a user to plain /auth/github
, they will be authenticated using the standard, read-only scope. But if I redirect them to /auth/github?role=shopkeeper
, they will be authenticated with the “repo” scope as well, giving the app the ability to manager their public and private repositories.
Thanks for the post Avdi. Any advise on learning Rack and Middleware? I haven’t been able to wrap my head around Omniauth because of them. Or maybe the RR folks can do a discussion on that topic 🙂
My suggestion is to just read the Rack docs and the source code for some middlewares. Then write your own middleware that does something silly like convert pages to pig latin.
The same works for options[:site] ?
I’m trying but it seems that its not working: http://stackoverflow.com/questions/22972569/uriinvalidurierror-in-callback-phase-using-omniauth2-gem-what-am-i-doing-wron