├── examples ├── rails-todo-list-app │ ├── public │ │ ├── favicon.ico │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── app │ │ ├── assets │ │ │ ├── stylesheets │ │ │ │ └── application.css │ │ │ └── javascripts │ │ │ │ └── application.js │ │ ├── controllers │ │ │ ├── home_controller.rb │ │ │ ├── application_controller.rb │ │ │ ├── signed_in_controller.rb │ │ │ ├── profile_controller.rb │ │ │ ├── sessions_controller.rb │ │ │ └── tasks_controller.rb │ │ ├── views │ │ │ ├── home │ │ │ │ └── index.html.haml │ │ │ ├── layouts │ │ │ │ ├── application.html.haml │ │ │ │ └── signed_in.html.haml │ │ │ ├── tasks │ │ │ │ └── index.html.haml │ │ │ └── profile │ │ │ │ └── index.html.haml │ │ └── models │ │ │ ├── task.rb │ │ │ └── user.rb │ ├── Rakefile │ ├── bin │ │ ├── rake │ │ ├── bundle │ │ ├── rails │ │ └── setup │ ├── config │ │ ├── boot.rb │ │ ├── initializers │ │ │ ├── omniauth.rb │ │ │ ├── cookies_serializer.rb │ │ │ ├── session_store.rb │ │ │ ├── mime_types.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── assets.rb │ │ │ ├── wrap_parameters.rb │ │ │ └── inflections.rb │ │ ├── environment.rb │ │ ├── database.yml │ │ ├── routes.rb │ │ ├── secrets.yml │ │ ├── application.rb │ │ └── environments │ │ │ ├── development.rb │ │ │ ├── test.rb │ │ │ └── production.rb │ ├── config.ru │ ├── .gitignore │ ├── Gemfile │ ├── db │ │ └── schema.rb │ └── README.md └── sinatra-multiple-providers-app │ ├── Gemfile │ ├── README.md │ ├── .env │ ├── config.ru │ └── app.rb ├── Gemfile ├── .travis.yml ├── .gitignore ├── .rubocop.yml ├── spec ├── fixtures │ ├── x5c_different.txt │ ├── x5c.txt │ ├── id_token_bad_issuer.txt │ ├── id_token_bad_nonce.txt │ ├── id_token_bad_audience.txt │ ├── id_token.txt │ ├── id_token_no_alg.txt │ ├── id_token_bad_chash.txt │ └── id_token_bad_kid.txt ├── spec_helper.rb └── omniauth │ └── strategies │ └── azure_activedirectory_spec.rb ├── Rakefile ├── .rubocop_todo.yml ├── omniauth-azure-activedirectory.gemspec ├── LICENSE.txt ├── lib ├── omniauth-azure-activedirectory.rb └── omniauth │ ├── azure_activedirectory.rb │ ├── azure_activedirectory │ └── version.rb │ └── strategies │ └── azure_activedirectory.rb ├── README.md └── RELEASES.md /examples/rails-todo-list-app/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - 2.1 5 | - 2.2 6 | 7 | script: bundle exec rake spec 8 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | //= require_tree . 2 | //= require_self 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.log 3 | .bundle 4 | coverage 5 | .powenv 6 | .rspec 7 | Gemfile.lock 8 | pkg/* 9 | tmp 10 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | end 3 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/Rakefile: -------------------------------------------------------------------------------- 1 | require File.expand_path('../config/application', __FILE__) 2 | 3 | Rails.application.load_tasks 4 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | Exclude: 5 | - 'spec/fixtures/**/*' 6 | 7 | Style/Encoding: 8 | Enabled: false 9 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | //= require jquery 2 | //= require jquery_ujs 3 | //= require turbolinks 4 | //= require_tree . 5 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | end 4 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/initializers/omniauth.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.middleware.use OmniAuth::Builder do 2 | provider :azure_activedirectory, ENV['CLIENT_ID'], ENV['TENANT'] 3 | end 4 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/views/home/index.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | To use this Todo List Manager, you will need to sign in with your Azure Active Directory credentials 3 | 4 | = link_to "Sign in", sign_in_path 5 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_rails-todo-list-app_session' 4 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /examples/sinatra-multiple-providers-app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'dotenv' 4 | gem 'sinatra' 5 | gem 'omniauth-amazon' 6 | gem 'omniauth-azure-activedirectory', path: '../../' 7 | gem 'omniauth-github' 8 | gem 'omniauth-google-oauth2' 9 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/models/task.rb: -------------------------------------------------------------------------------- 1 | class Task < ActiveRecord::Base 2 | belongs_to :user 3 | validates :user_id, :description, presence: true 4 | 5 | # When a task is completed, we remove it from the database. 6 | # This cannot be undone. 7 | def complete 8 | Task.find(id).destroy 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %title RailsTodoListApp 5 | = stylesheet_link_tag "application", media: "all" 6 | = javascript_include_tag "application" 7 | = csrf_meta_tags 8 | %body 9 | %h1 10 | Todo List 11 | #main 12 | = yield 13 | -------------------------------------------------------------------------------- /examples/sinatra-multiple-providers-app/README.md: -------------------------------------------------------------------------------- 1 | This example shows how OmniAuth can be used with many different providers, including AzureAD. 2 | 3 | Before running this sample, you should install the dependencies with: 4 | 5 | ``` 6 | bundle install 7 | ``` 8 | 9 | You can then run the sample with: 10 | 11 | ``` 12 | bundle exec rackup 13 | ``` 14 | -------------------------------------------------------------------------------- /examples/sinatra-multiple-providers-app/.env: -------------------------------------------------------------------------------- 1 | AAD_KEY= 2 | AAD_TENANT= 3 | 4 | AMAZON_KEY= 5 | AMAZON_SECRET= 6 | 7 | GITHUB_KEY= 8 | GITHUB_SECRET= 9 | 10 | GOOGLE_KEY= 11 | GOOGLE_SECRET= 12 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/views/tasks/index.html.haml: -------------------------------------------------------------------------------- 1 | #task_index 2 | %h3 Add a new task 3 | = form_for(@new_task) do |form| 4 | = form.text_field :description 5 | = form.submit 6 | 7 | %h3 Your tasks 8 | - @tasks.each do |task| 9 | %li.task 10 | = button_to 'Delete', task_path(task), method: :delete, data: { confirm: 'Are you sure?' }, form: { style: 'display:inline-block;' } 11 | = label :description, task.description 12 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | capybara-*.html 3 | rspec 4 | /log 5 | /tmp 6 | /public/system 7 | /coverage/ 8 | /spec/tmp 9 | **.orig 10 | rerun.txt 11 | pickle-email-*.html 12 | config/secrets.yml 13 | config/initializers/secret_token.rb 14 | /vendor/bundle 15 | .rvmrc 16 | /vendor/assets/bower_components 17 | *.bowerrc 18 | bower.json 19 | .powenv 20 | /.bundle 21 | /db/*.sqlite3 22 | /db/*.sqlite3-journal 23 | /log/* 24 | !/log/.keep 25 | /tmp 26 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/environment.rb: -------------------------------------------------------------------------------- 1 | # You may want to specify these keys separately for production and test 2 | # environments. 3 | ENV['CLIENT_ID'] = 'YOUR CLIENT ID HERE' 4 | ENV['CLIENT_SECRET'] = 'YOUR CLIENT SECRET HERE' 5 | ENV['TENANT'] ='YOUR TENANT HERE' 6 | 7 | # Load the Rails application. 8 | require File.expand_path('../application', __FILE__) 9 | 10 | ADAL::Logging.log_level = ADAL::Logger::VERBOSE 11 | 12 | # Initialize the Rails application. 13 | Rails.application.initialize! 14 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/views/profile/index.html.haml: -------------------------------------------------------------------------------- 1 | %h4 This is your personal information from the AzureAD graph 2 | %br 3 | = label_tag :name, "Name: #{@profile['displayName']}" 4 | %br 5 | = label_tag :phone, "Phone: #{@profile['mobile']}" 6 | %br 7 | = label_tag :position, "Position: #{@profile['jobTitle']}" 8 | %br 9 | = label_tag :address, "Address: #{@profile['streetAddress']}" 10 | %br 11 | = label_tag :postal_code, "Postal code: #{@profile['postalCode']}" 12 | %br 13 | = label_tag :country, "Country: #{@profile['country']}" 14 | -------------------------------------------------------------------------------- /spec/fixtures/x5c_different.txt: -------------------------------------------------------------------------------- 1 | MIIBjTCCAYGgAwIBAgIBATADBgEAMBUxEzARBgoJkiaJk_IsZAEZFgNvcmcwHhcNMTUwODA3MjAyMDU1WhcNMjUwODA0MjAyMDU1WjAVMRMwEQYKCZImiZPyLGQBGRYDb3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8VyE4KHx0GDV6RMYqscBR56lal9uQNYXm_Du8O6-F8Da9Xc0h7O8JWomqFLUEE-0ogMcjzOAiQzTc8X6VhkIBbMCKYA6OQ-hiC3aaYFLNAnxaVThnUJWXxA7spbnKFG1xZK01G8bAx7s7DbbWMHlchebjWprCMkYEjnECXVuDyfkZW-8aHDCtq62JAd-WQL1LN-UkOBCgodTNW2x7e-_KBvPzPSSzYygAh7kCl727QeHwplgA83mQrdecNvYoiEBYOjSKz8bdiKRYjmogGoDv3_W4cY76a9XBZpSoNjBWdWo_w7ce4KbOsi3V0g8EZm9ccKjBPKe9pZYF4aCNQJ6QIDAQABMAMGAQADAQA -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/views/layouts/signed_in.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %title RailsTodoListApp 5 | = stylesheet_link_tag "application", media: "all" 6 | = javascript_include_tag "application" 7 | = csrf_meta_tags 8 | %body 9 | %h1 10 | Todo List 11 | %p 12 | #{@current_user.name} <#{@current_user.email}>. 13 | %br 14 | = link_to "Todo List", tasks_path 15 | = link_to "Profile", profile_index_path 16 | = link_to "Sign out", session_path, method: :delete 17 | #main 18 | = yield 19 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /spec/fixtures/x5c.txt: -------------------------------------------------------------------------------- 1 | MIIBwzCCAbegAwIBAgIBATADBgEAMDAxEzARBgoJkiaJk_IsZAEZFgNvcmcxGTAXBgoJkiaJk_IsZAEZFglydWJ5LWxhbmcwHhcNMTUwODA2MjMyOTE4WhcNMjUwODAzMjMyOTE4WjAwMRMwEQYKCZImiZPyLGQBGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxyunSConbK4z1T47vukPa0OaNcY_R6l8Z0TILR1w5O_YkMbJGRXpZORFPVs83xLoxs7RcPWnibQhQ4G6m6fwEA4rutvOm-3xWyey-OOZdVqpNmxqd2VcCCI6AoUtl8m4w0UFuu-oiELj7BF8cGS5wvHYATEBEY72n_84uu-LF423Ffe8HeMEY00nO3ZVcV9MjFBVps8RAwL62ooXPZyly6fQt4728wlZPs3EMijS-Bj4auokx6ssBooaF9XiZJKKCAe16epbBB9S4XVlNXo6EhZ-PBpMFRJknDwWFFYPVOX3NlBp8chi8VaXmDXqsFJiwkkeIqg4I6WFAM4BsnDInwIDAQABMAMGAQADAQA -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | 3 | require 'rake' 4 | require 'rspec/core/rake_task' 5 | require 'rubocop/rake_task' 6 | 7 | # This can be run with `bundle exec rake spec`. 8 | RSpec::Core::RakeTask.new(:spec) do |t| 9 | t.pattern = `git ls-files`.split("\n").select { |f| f.end_with? 'spec.rb' } 10 | t.rspec_opts = '--format documentation' 11 | end 12 | 13 | # This can be run with `bundle exec rake rubocop`. 14 | RuboCop::RakeTask.new(:rubocop) do |t| 15 | t.patterns = `git ls-files`.split("\n").select do |f| 16 | f.end_with?('.rb') && !f.start_with?('examples') 17 | 18 | end 19 | t.fail_on_error = false 20 | end 21 | 22 | task default: :spec 23 | -------------------------------------------------------------------------------- /spec/fixtures/id_token_bad_issuer.txt: -------------------------------------------------------------------------------- 1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyJ9.eyJpc3MiOiJodHRwczovL3N0cy5pbXBvc3Rlci5uZXQvYnVuY2gtb2YtcmFuZG9tLWNoYXJzIiwibmFtZSI6IkpvaG4gU21pdGgiLCJhdWQiOiJ0aGUgY2xpZW50IGlkIiwibm9uY2UiOiJteSBub25jZSIsImdpdmVuX25hbWUiOiJKb2huIiwiZmFtaWx5X25hbWUiOiJzbWl0aCIsImVtYWlsIjoianNtaXRoQGNvbnRvc28uY29tIn0.fwnJuRsif_Td3MfXoyADHidYJyPFdWSBBoLbVAu4Lz3-pmSln9Vgxl5KowqEKq2LX5n0aqyWVGLcoT-_G_PNXuizuNmssv5vreKLvDMpsFXt2irdwGYDCRki7KQPBk3bn12YjBzE2EqiRy7dTEG_0vWoh1RqoNP72BBL8xYQUlIOFleZhT5KGNYbh7rvcmDq7aA-xdaXT3QJfHCHpitW_zVzZ5Gok_awcdx_v3r3eFbG2IT0PfmT40Ljia0aP2i60KgsOLLHarYO51KFNDEfr1pUDf4IweaEzstbVLwk-_5ulJ5QgByhNJrmWDfrGeCQRk0SO2XX-EUsVn5ySVJ5CQ -------------------------------------------------------------------------------- /spec/fixtures/id_token_bad_nonce.txt: -------------------------------------------------------------------------------- 1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyJ9.eyJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9idW5jaC1vZi1yYW5kb20tY2hhcnMiLCJuYW1lIjoiSm9obiBTbWl0aCIsImF1ZCI6InRoZSBjbGllbnQgaWQiLCJub25jZSI6Im5vdCBteSBub25jZSIsImdpdmVuX25hbWUiOiJKb2huIiwiZmFtaWx5X25hbWUiOiJzbWl0aCIsImVtYWlsIjoianNtaXRoQGNvbnRvc28uY29tIn0.KBSOCiy0sUF33akept9nPNFgmMWxiPWDBVA0dZqQaF6gQkhQu82irAQzB2Ygkh9KDeIEf0MSZWLDq0X3W3Fke4wzjrbzL-2QM4l-KgPFbJixqtJYHSPOidHSCQ8vA0v8kES3H_zqU6QisygwwXLh2ozqKXMnsrBPIAtiZz_a0vPbHtrYbb-WIrtTruMemcTt5OkbfDIttzi5EarakQg93vraIb0jK0szuAqLkXFOzcIIGPgyAvVpHZveqm99GR04tbKyTRIyJP1vZwtIpu89PKFM3soWcWd_kjAWcS81ZTzEn5_P-1QL7YTInK53acNXZHh08Fba3Um4J_KlV2KRjw -------------------------------------------------------------------------------- /spec/fixtures/id_token_bad_audience.txt: -------------------------------------------------------------------------------- 1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyJ9.eyJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9idW5jaC1vZi1yYW5kb20tY2hhcnMiLCJuYW1lIjoiSm9obiBTbWl0aCIsImF1ZCI6Im5vdCB0aGUgY2xpZW50IGlkIiwibm9uY2UiOiJteSBub25jZSIsImdpdmVuX25hbWUiOiJKb2huIiwiZmFtaWx5X25hbWUiOiJzbWl0aCIsImVtYWlsIjoianNtaXRoQGNvbnRvc28uY29tIn0.gGCFTcGDBb2P5tPRj2ojUUiwOJoSslQlxEVTElZY6FCzCZzcypsnrYOB9Adkztp2AWF4wW9fT58lqXwaahKquXtK4wyK4KoNBxXBhS4sDMeNpVkK4914tT_6gecvyUsI_tlJaS0epd5c0mN9-1QjgvNEirY1L-XYaT290LmLYVYIFTEJXoSlnwvv081k0txdJZKr14JXd_bSLUbhGSd-NcGZkJuVmg2F_C65zd1wUsQOkV2iVdJ-ycaDJklv8-DFfDFIHQio8S9yqyieHwArRmTW9HzcoHhWBh2MIItszoTSbmQEF062NtNBPW8gyk1OSot5X9klJUhgPAAFqJ0TJQ -------------------------------------------------------------------------------- /spec/fixtures/id_token.txt: -------------------------------------------------------------------------------- 1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyJ9.eyJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9idW5jaC1vZi1yYW5kb20tY2hhcnMiLCJuYW1lIjoiSm9obiBTbWl0aCIsImF1ZCI6InRoZSBjbGllbnQgaWQiLCJub25jZSI6Im15IG5vbmNlIiwiZ2l2ZW5fbmFtZSI6IkpvaG4iLCJmYW1pbHlfbmFtZSI6InNtaXRoIiwiZW1haWwiOiJqc21pdGhAY29udG9zby5jb20iLCJjX2hhc2giOiJWcFRRaWk1VF84cmd3eEEtV3RiMkJ3In0.Xz9SL1dm9xeJ3YtBIwSpL7SHEMr5lsL32mkJIugoAt7rNhj2ZauN77_N3skU9FIRTVb_XBFHrLo1AXion7RWoOGAMk8xnuQRlhamGoWsjttWE9oO6J6kuQPSDBvLTA88UqLoGNezDwFNfgUFQq-66m33ZWdkiNOFoFZ_In6DtAwxHZZUys-KoYD3iCbviUoBzU57aV-SBsWyComq39pDGpw03qZoa_xgRfujdVHG1DKlO5VG79kUE3ySYWJyBYVKUdAzjH1iotjpPA1svtqytn4CUldAMi4nnf7iq5SCJMb4ucu0mN6AhJH9ktY--fGY6_OidyiDe4F57ZzLw-3jOQ -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /spec/fixtures/id_token_no_alg.txt: -------------------------------------------------------------------------------- 1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyJ9.eyJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9idW5jaC1vZi1yYW5kb20tY2hhcnMiLCJuYW1lIjoiSm9obiBTbWl0aCIsImF1ZCI6InRoZSBjbGllbnQgaWQiLCJub25jZSI6Im15IG5vbmNlIiwiZ2l2ZW5fbmFtZSI6IkpvaG4iLCJmYW1pbHlfbmFtZSI6InNtaXRoIiwiZW1haWwiOiJqc21pdGhAY29udG9zby5jb20iLCJjX2hhc2giOiJWcFRRaWk1VF84cmd3eEEtV3RiMkJ3In0.Xz9SL1dm9xeJ3YtBIwSpL7SHEMr5lsL32mkJIugoAt7rNhj2ZauN77_N3skU9FIRTVb_XBFHrLo1AXion7RWoOGAMk8xnuQRlhamGoWsjttWE9oO6J6kuQPSDBvLTA88UqLoGNezDwFNfgUFQq-66m33ZWdkiNOFoFZ_In6DtAwxHZZUys-KoYD3iCbviUoBzU57aV-SBsWyComq39pDGpw03qZoa_xgRfujdVHG1DKlO5VG79kUE3ySYWJyBYVKUdAzjH1iotjpPA1svtqytn4CUldAMi4nnf7iq5SCJMb4ucu0mN6AhJH9ktY--fGY6_OidyiDe4F57ZzLw-3jOQ -------------------------------------------------------------------------------- /spec/fixtures/id_token_bad_chash.txt: -------------------------------------------------------------------------------- 1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyJ9.eyJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9idW5jaC1vZi1yYW5kb20tY2hhcnMiLCJuYW1lIjoiSm9obiBTbWl0aCIsImF1ZCI6InRoZSBjbGllbnQgaWQiLCJub25jZSI6Im15IG5vbmNlIiwiZ2l2ZW5fbmFtZSI6IkpvaG4iLCJmYW1pbHlfbmFtZSI6InNtaXRoIiwiZW1haWwiOiJqc21pdGhAY29udG9zby5jb20iLCJjX2hhc2giOiI1WEhDdk9qcUgtbnJBXzNXNUJYaWJnIn0.EHPFrxMbr2EH1IZ15F3oP1yvrukadY4ggZSOxm625qPoMxfhv-QzFm0YMhkAG0cLM_LSHi_RgedDwTGuzOtIWqmheYzydnhtbIBeKmx0RSgdob6WeiTZwJ93VRiV4q82Qda41JaaIl_wdWd4lVyVstd9o9jPYMtKEVLgaNDrtHt6pPxEGCVraiaM6tVyKc5XIHu3wNaqle5UZZREU6oirwTCUhXDZkz1g5qY2-aWUdfFVsElSarJuMcxGDPD20hvx7T3D7SCHAF8WhKw3AwQyrodKWCo3cqDOz6vHymb_-ELkJc14GMbtJiyrf6XYySZZoseoI_WBDmLfKcK637xCw -------------------------------------------------------------------------------- /spec/fixtures/id_token_bad_kid.txt: -------------------------------------------------------------------------------- 1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMzQifQ.eyJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9idW5jaC1vZi1yYW5kb20tY2hhcnMiLCJuYW1lIjoiSm9obiBTbWl0aCIsImF1ZCI6InRoZSBjbGllbnQgaWQiLCJub25jZSI6Im15IG5vbmNlIiwiZ2l2ZW5fbmFtZSI6IkpvaG4iLCJmYW1pbHlfbmFtZSI6InNtaXRoIiwiZW1haWwiOiJqc21pdGhAY29udG9zby5jb20iLCJjX2hhc2giOiI1WEhDdk9qcUgtbnJBXzNXNUJYaWJnIn0.gWGBc9rH30SN17Ikm1CjqIYAyzFHX0yeRQu85sVYLE3r5k26bjS3R6rTJcCQlYqHPRdoPcnkUgT1QVbdThw34ICrODoavs7I5QYEn_jKP9zM4UJEKQaCLBAtitzrk1KDEf0GLcNKif-MYu7MiQfoOzwCGWfIs-vgqk4lv0JUs9OlSLp5LHru7G3jKy6Qswbrpxpjzm9I8BnKEdhUfhz4P6wIf9KLMmhgeGQdQ6wBuxPmOf9r6EKIij2AENhFp1qP90m8kXq9tIt5FZFjwIs_G6spLl0gQXyx0qC8rP5JTkqwrBUieWU-BRqVdax8YxA0iDKzZyfsMV92yVcZT6S_NQ -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by `rubocop --auto-gen-config` 2 | # on 2015-08-06 14:09:24 -0700 using RuboCop version 0.32.1. 3 | # The point is for the user to remove these configuration records 4 | # one by one as the offenses are removed from the code base. 5 | # Note that changes in the inspected code, or installation of new 6 | # versions of RuboCop, may require this file to be generated again. 7 | 8 | # Offense count: 2 9 | Metrics/AbcSize: 10 | Max: 19 11 | 12 | # Offense count: 1 13 | # Configuration parameters: CountComments. 14 | Metrics/ClassLength: 15 | Max: 118 16 | 17 | # Offense count: 1 18 | # Configuration parameters: Exclude. 19 | Style/FileName: 20 | Enabled: false 21 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/controllers/signed_in_controller.rb: -------------------------------------------------------------------------------- 1 | # Any controllers that require users to sign in should extend this class. 2 | class SignedInController < ApplicationController 3 | before_filter :require_sign_in 4 | skip_before_filter :verify_authenticity_token 5 | 6 | def index 7 | @current_user = current_user 8 | render :index 9 | end 10 | 11 | def require_sign_in 12 | redirect_to root_path if current_user.nil? 13 | end 14 | 15 | def add_auth 16 | current_user.redeem_code( 17 | params['code'], 18 | 'https://localhost:9292/authorize') 19 | redirect_to profile_index_path 20 | end 21 | 22 | def current_user 23 | User.find_by_id(session['user_id']) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '4.2.1' 4 | 5 | # Stores the todo list. 6 | gem 'sqlite3' 7 | 8 | # Templating library for views. 9 | gem 'haml' 10 | 11 | # The actual web server. 12 | gem 'thin' 13 | 14 | # Rack middleware authentication framework. 15 | gem 'omniauth' 16 | 17 | # AzureAD specific strategy for OmniAuth. 18 | gem 'omniauth-azure-activedirectory' 19 | 20 | # Loads configurations from .env into ENV hash. 21 | gem 'dotenv' 22 | 23 | # Acquires access tokens for resources. 24 | gem 'adal' 25 | 26 | # For the front end. 27 | gem 'sass-rails', '~> 5.0' 28 | gem 'uglifier', '>= 1.3.0' 29 | gem 'coffee-rails', '~> 4.1.0' 30 | gem 'jquery-rails' 31 | gem 'jbuilder', '~> 2.0' 32 | gem 'sdoc', '~> 0.4.0', group: :doc 33 | gem 'turbolinks' 34 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/controllers/profile_controller.rb: -------------------------------------------------------------------------------- 1 | class ProfileController < SignedInController 2 | 3 | # If we have the user's ADAL credentials, then we can get an access token. 4 | # Otherwise we need to do the auth code flow dance. 5 | def index 6 | @profile = user_data_hash(current_user.graph_access_token) 7 | super 8 | rescue ADAL::TokenRequest::UserCredentialError 9 | redirect_to User.authorization_request_url.to_s 10 | end 11 | 12 | # @return Hash 13 | def user_data_hash(access_token) 14 | headers = { 'authorization' => access_token } 15 | me_endpt = URI('https://graph.windows.net/me?api-version=1.5') 16 | http = Net::HTTP.new(me_endpt.hostname, me_endpt.port) 17 | http.use_ssl = true 18 | JSON.parse(http.get(me_endpt, headers).body) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | skip_before_filter :verify_authenticity_token 3 | 4 | def new 5 | redirect_to '/auth/azureactivedirectory' 6 | end 7 | 8 | def create 9 | # If the session expires, they still access the same todo list next time 10 | # that they log in with omniauth. 11 | user = User.find_by_provider_and_uid(auth_hash['provider'], 12 | auth_hash['uid']) 13 | user ||= User.from_omniauth(auth_hash) 14 | session['user_id'] = user.id 15 | redirect_to tasks_path 16 | end 17 | 18 | def destroy 19 | reset_session 20 | redirect_to root_url 21 | end 22 | 23 | protected 24 | 25 | def auth_hash 26 | request.env['omniauth.auth'] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | root to: 'home#index' 3 | 4 | resource :session, only: [:destroy] 5 | resources :profile, only: [:index] 6 | resources :tasks, only: [:index, :update, :create, :destroy, :post] 7 | 8 | # This is where we send people to authenticate with OmniAuth. 9 | get '/auth/azureactivedirectory', as: :sign_in 10 | 11 | # This is where we are redirected if OmniAuth successfully authenicates 12 | # the user. 13 | match '/auth/:provider/callback', to: 'sessions#create', via: [:get, :post] 14 | 15 | # This is where we are redirected if we acquire authorization separately from 16 | # OmniAuth. 17 | match '/authorize', to: 'signed_in#add_auth', via: [:get, :post] 18 | 19 | # This is where we are redirected if OmniAuth fails to authenticate the user. 20 | # user 21 | match '/auth/:provider/failure', to: redirect('/'), via: [:get, :post] 22 | end 23 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/controllers/tasks_controller.rb: -------------------------------------------------------------------------------- 1 | class TasksController < SignedInController 2 | 3 | def index 4 | @new_task = Task.new 5 | @tasks = (current_user.tasks if current_user.respond_to? :tasks) || {} 6 | super 7 | end 8 | 9 | def create 10 | return unless params['task']['description'] # We don't store empty tasks. 11 | task = Task.create!(description: params['task']['description'], 12 | user_id: current_user.id, 13 | user: current_user) 14 | current_user.tasks.append task 15 | task.save! 16 | redirect_to tasks_path 17 | end 18 | 19 | def destroy 20 | Task.find(params[:id]).destroy 21 | redirect_to tasks_path 22 | end 23 | 24 | private 25 | 26 | def current_user 27 | User.find_by_id(session['user_id']) 28 | end 29 | 30 | def task_params 31 | params.require(:task).permit(:description) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: a394e1705b0cf219806474c110e9202d8fbf9606eb32193b3b8cee51b8b23ecfed815fea88fbbc09c71d7fe725db6b64cb067c450f70e5b8c654a51836872e8a 15 | 16 | test: 17 | secret_key_base: 308279ab342212586d95b99ab1bace239dcea43f96a10d21a5e8eb61141820db35f6d7eb1f7b75ac6fadd25d7404f5dd9d93c7a8246d935200352b7ffc4c98a7 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /omniauth-azure-activedirectory.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.push File.expand_path('../lib', __FILE__) 2 | require 'omniauth/azure_activedirectory/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'omniauth-azure-activedirectory' 6 | s.version = OmniAuth::AzureActiveDirectory::VERSION 7 | s.author = 'Microsoft Corporation' 8 | s.email = 'nugetaad@microsoft.com' 9 | s.summary = 'Azure Active Directory strategy for OmniAuth' 10 | s.description = 'Allows developers to authenticate to AAD' 11 | s.homepage = 'https://github.com/AzureAD/omniauth-azure-activedirectory' 12 | s.license = 'MIT' 13 | 14 | s.files = `git ls-files`.split("\n") 15 | s.require_paths = ['lib'] 16 | 17 | s.add_runtime_dependency 'jwt', '~> 1.5' 18 | s.add_runtime_dependency 'omniauth', '~> 1.1' 19 | 20 | s.add_development_dependency 'rake', '~> 10.4' 21 | s.add_development_dependency 'rspec', '~> 3.3' 22 | s.add_development_dependency 'rubocop', '~> 0.32' 23 | s.add_development_dependency 'simplecov', '~> 0.10' 24 | s.add_development_dependency 'webmock', '~> 1.21' 25 | end 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Micorosft Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module RailsTodoListApp 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 21 | # config.i18n.default_locale = :de 22 | 23 | # Do not swallow errors in after_commit/after_rollback callbacks. 24 | config.active_record.raise_in_transactional_callbacks = true 25 | 26 | # All traffic must be HTTPS. 27 | config.force_ssl = true 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/omniauth-azure-activedirectory.rb: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Copyright (c) 2015 Micorosft Corporation 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | #------------------------------------------------------------------------------- 22 | 23 | require 'omniauth/azure_activedirectory' 24 | -------------------------------------------------------------------------------- /lib/omniauth/azure_activedirectory.rb: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Copyright (c) 2015 Micorosft Corporation 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | #------------------------------------------------------------------------------- 22 | 23 | require 'omniauth/azure_activedirectory/version' 24 | require 'omniauth/strategies/azure_activedirectory' 25 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20150819163233) do 15 | 16 | create_table "tasks", force: :cascade do |t| 17 | t.string "user_id" 18 | t.text "description" 19 | t.date "due_date" 20 | t.datetime "created_at", null: false 21 | t.datetime "updated_at", null: false 22 | end 23 | 24 | create_table "users", force: :cascade do |t| 25 | t.string "uid" 26 | t.string "provider" 27 | t.string "name" 28 | t.string "email" 29 | t.datetime "created_at", null: false 30 | t.datetime "updated_at", null: false 31 | t.string "adal_unique_id" 32 | t.string "adal_displayable_id" 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /lib/omniauth/azure_activedirectory/version.rb: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Copyright (c) 2015 Micorosft Corporation 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | #------------------------------------------------------------------------------- 22 | 23 | module OmniAuth 24 | # The release version. 25 | module AzureActiveDirectory 26 | VERSION = '1.0.0' 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 31 | # yet still be able to expire them through the digest params. 32 | config.assets.digest = true 33 | 34 | # Adds additional error checking when serving assets at runtime. 35 | # Checks for improperly declared sprockets dependencies. 36 | # Raises helpful error messages. 37 | config.assets.raise_runtime_errors = true 38 | 39 | # Raises error for missing translations 40 | # config.action_view.raise_on_missing_translations = true 41 | end 42 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Copyright (c) 2015 Micorosft Corporation 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | #------------------------------------------------------------------------------- 22 | 23 | require 'webmock/rspec' 24 | require 'simplecov' 25 | 26 | SimpleCov.start do 27 | # Don't measure coverage on test files. 28 | add_filter 'spec' 29 | end 30 | 31 | WebMock.disable_net_connect!(allow_localhost: true) 32 | 33 | RSpec.configure do |config| 34 | config.expect_with :rspec do |expectations| 35 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 36 | end 37 | 38 | config.mock_with :rspec do |mocks| 39 | mocks.verify_partial_doubles = true 40 | end 41 | 42 | config.warnings = true 43 | config.order = :random 44 | end 45 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | has_many :tasks 3 | validates :provider, :uid, presence: true 4 | 5 | AUTH_CTX = ADAL::AuthenticationContext.new( 6 | 'login.windows.net', ENV['TENANT']) 7 | CLIENT_CRED = ADAL::ClientCredential.new(ENV['CLIENT_ID'], ENV['CLIENT_SECRET']) 8 | GRAPH_RESOURCE = 'https://graph.windows.net' 9 | 10 | def self.from_omniauth(auth_hash) 11 | user = User.where(provider: auth_hash[:provider], 12 | uid: auth_hash[:uid]).first_or_create 13 | user.name = auth_hash[:info]['name'] 14 | user.email = auth_hash[:info]['email'] 15 | 16 | # Note that this is the first part that is AAD specific. 17 | if auth_hash[:credentials]['code'] 18 | user.redeem_code( 19 | auth_hash[:credentials]['code'], 20 | 'https://localhost:9292/auth/azureactivedirectory/callback') 21 | end 22 | user.save! 23 | user 24 | end 25 | 26 | def graph_access_token 27 | AUTH_CTX.acquire_token_for_user(GRAPH_RESOURCE, CLIENT_CRED, adal_user_identifier).access_token 28 | end 29 | 30 | def redeem_code(auth_code, reply_url) 31 | adal_user = AUTH_CTX.acquire_token_with_authorization_code( 32 | auth_code, 33 | reply_url, 34 | CLIENT_CRED, 35 | GRAPH_RESOURCE 36 | ).user_info 37 | self.adal_unique_id = adal_user.unique_id 38 | self.adal_displayable_id = adal_user.displayable_id 39 | self.save! 40 | end 41 | 42 | def self.authorization_request_url 43 | AUTH_CTX.authorization_request_url( 44 | GRAPH_RESOURCE, 45 | ENV['CLIENT_ID'], 46 | 'https://localhost:9292/authorize') 47 | end 48 | 49 | private 50 | 51 | def adal_user_identifier 52 | if adal_displayable_id 53 | ADAL::UserIdentifier.new(adal_displayable_id, :DISPLAYABLE_ID) 54 | else 55 | ADAL::UserIdentifier.new(adal_unique_id, :UNIQUE_ID) 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /examples/sinatra-multiple-providers-app/config.ru: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Copyright (c) 2015 Micorosft Corporation 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | #------------------------------------------------------------------------------- 22 | 23 | require 'dotenv' 24 | require 'omniauth' 25 | require 'omniauth-amazon' 26 | require 'omniauth-azure-activedirectory' 27 | require 'omniauth-github' 28 | require 'omniauth-google-oauth2' 29 | 30 | # Load API keys from .env 31 | Dotenv.load 32 | 33 | require_relative './app.rb' 34 | 35 | # You must provide a session to use OmniAuth. 36 | use Rack::Session::Cookie, secret: 'top secret' 37 | 38 | use OmniAuth::Builder do 39 | provider :amazon, ENV['AMAZON_KEY'], ENV['AMAZON_SECRET'] 40 | provider :azure_activedirectory, ENV['AAD_KEY'], ENV['AAD_TENANT'] 41 | provider :github, ENV['GITHUB_KEY'], ENV['GITHUB_SECRET'] 42 | provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'] 43 | end 44 | 45 | run Sinatra::Application 46 | -------------------------------------------------------------------------------- /examples/sinatra-multiple-providers-app/app.rb: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Copyright (c) 2015 Micorosft Corporation 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | #------------------------------------------------------------------------------- 22 | 23 | require 'sinatra' 24 | 25 | # Configure Sinatra. 26 | set :run, false 27 | set :raise_errors, true 28 | 29 | get '/' do 30 | content_type 'text/html' 31 | <<-HTML 32 |

Hello there!

33 | Sign in with Amazon 34 | Sign in with AzureAD 35 | Sign in with Github 36 | Sign in with Google 37 | HTML 38 | end 39 | 40 | %w(get post).each do |method| 41 | send(method, '/auth/:provider/callback') do 42 | auth = request.env['omniauth.auth'] 43 | "Your authentication looks like #{JSON.unparse(auth)}." 44 | end 45 | end 46 | 47 | %w(get post).each do |method| 48 | send(method, '/auth/:provider/failure') do 49 | "Aw shucks, we couldn't verify your identity!" 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/README.md: -------------------------------------------------------------------------------- 1 | Rails, OmniAuth and Graph API 2 | ============================= 3 | 4 | This is a sample MVC web application that demonstrates user authentication with OmniAuth for Azure Active Directory and RESTful calls to the AzureAD Graph API with ADAL Ruby. 5 | 6 | ## How to run this sample 7 | 8 | To run this sample you will need 9 | - [Ruby](https://www.ruby-lang.org/en/documentation/installation/) 10 | - [Bundler](http://bundler.io) 11 | - An internet connection 12 | - An Azure subscription (a free trial is sufficient) 13 | 14 | ### Step 1 - Install ADAL from source 15 | Note: This can and should be removed once ADAL is available on RubyGems. After that point ADAL will be installed along with the other dependencies in step 3. 16 | 17 | ``` 18 | git clone git@github.com:AzureAD/azure-activedirectory-library-for-ruby 19 | cd azure-activedirectory-library-for-ruby 20 | gem build adal.gemspec 21 | gem install adal 22 | ``` 23 | 24 | ### Step 2 - Install OmniAuth AzureAD from source 25 | Note: This can and should be removed once ADAL is available on RubyGems. After that point ADAL will be installed along with the other dependencies in step 3. 26 | 27 | ``` 28 | git clone git@github.com:AzureAD/omniauth-azure-activedirectory-priv 29 | cd omniauth-azure-activedirectory-priv 30 | gem build omniauth-azure-activedirectory.gemspec 31 | gem install omniauth-azure-activedirectory 32 | ``` 33 | 34 | ### Step 3 - Install the sample dependencies 35 | 36 | ``` 37 | cd examples/rails-todo-list-app 38 | bundle 39 | ``` 40 | 41 | ### Step 4 - Set up the database 42 | 43 | ``` 44 | rake db:schema:load 45 | ``` 46 | 47 | Note: Depending on your host environment, you may need to install a Javascript runtime. We suggest Node.js. Installation will differ by platform. 48 | 49 | ### Step 5 - Configure the app 50 | 51 | Open `config/environment.rb` and replace the `CLIENT_ID`, `CLIENT_SECRET` and `TENANT` with your values. 52 | 53 | ### Step 6 - Set up SSL 54 | 55 | This step is optional to get the sample running and varies across platform and choice of webserver. Here we will present one set of instructions to accomplish this, but there are many others. 56 | 57 | Generate a self-signed certificate. 58 | 59 | ``` 60 | openssl req -new -newkey rsa:2048 -sha1 -days 365 -nodes -x509 -keyout server.key -out server.crt 61 | ``` 62 | 63 | Get your machine/browser to trust the certificate. This varies wildly by platform. 64 | 65 | On OSX with Safari or Chrome, double click on `server.crt` in Finder to add it to the keychain and then select 'Trust Always'. In Firefox go to Preferences > Advanced > View Certificates > Import and add `server.crt`. 66 | 67 | ### Step 7 - Start up Rails 68 | 69 | This sample uses the Thin webserver to host the app on port 9292. 70 | 71 | If you generated a certificate in Step 6 72 | 73 | ``` 74 | bundle exec thin start --port 9292 --ssl --ssl-key-file server.key --ssl-cert-file server.crt 75 | ``` 76 | 77 | If you want to skip SSL verification (shame!) 78 | 79 | ``` 80 | bundle exec thing start --port 9292 --ssl --ssl-disable-verify 81 | ``` 82 | 83 | You may now proceed to https://localhost:9292 to view the application. You may get a warning about the self-signed certificate. This is nothing to worry about, as in production you will not be using self-signed certs. 84 | -------------------------------------------------------------------------------- /examples/rails-todo-list-app/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | # config.force_ssl = true 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | # Prepend all log lines with the following tags. 52 | # config.log_tags = [ :subdomain, :uuid ] 53 | 54 | # Use a different logger for distributed setups. 55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 61 | # config.action_controller.asset_host = 'http://assets.example.com' 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Do not dump schema after migrations. 78 | config.active_record.dump_schema_after_migration = false 79 | end 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OmniAuth Azure Active Directory 2 | [![Build Status](https://travis-ci.org/AzureAD/omniauth-azure-activedirectory.png?branch=master)](https://travis-ci.org/AzureAD/omniauth-azure-activedirectory) 3 | [![Code Climate](https://codeclimate.com/github/AzureAD/omniauth-azure-activedirectory/badges/gpa.svg)](https://codeclimate.com/github/AzureAD/omniauth-azure-activedirectory/badges/gpa.svg) 4 | 5 | OmniAuth strategy to authenticate to Azure Active Directory via OpenId Connect. 6 | 7 | Before starting, set up a tenant and register a Web Application at [https://manage.windowsazure.com](https://manage.windowsazure.com). Note your client id and tenant for later. 8 | 9 | ## Samples and Documentation 10 | 11 | [We provide a full suite of sample applications and documentation on GitHub](https://github.com/AzureADSamples) to help you get started with learning the Azure Identity system. This includes tutorials for native clients such as Windows, Windows Phone, iOS, OSX, Android, and Linux. We also provide full walkthroughs for authentication flows such as OAuth2, OpenID Connect, Graph API, and other awesome features. 12 | 13 | ## Community Help and Support 14 | 15 | We leverage [Stack Overflow](http://stackoverflow.com/) to work with the community on supporting Azure Active Directory and its SDKs, including this one! We highly recommend you ask your questions on Stack Overflow (we're all on there!) Also browser existing issues to see if someone has had your question before. 16 | 17 | We recommend you use the "adal" tag so we can see it! Here is the latest Q&A on Stack Overflow for ADAL: [http://stackoverflow.com/questions/tagged/adal](http://stackoverflow.com/questions/tagged/adal) 18 | 19 | ## Security Reporting 20 | 21 | If you find a security issue with our libraries or services please report it to [secure@microsoft.com](mailto:secure@microsoft.com) with as much detail as possible. Your submission may be eligible for a bounty through the [Microsoft Bounty](http://aka.ms/bugbounty) program. Please do not post security issues to GitHub Issues or any other public site. We will contact you shortly upon receiving the information. We encourage you to get notifications of when security incidents occur by visiting [this page](https://technet.microsoft.com/en-us/security/dd252948) and subscribing to Security Advisory Alerts. 22 | 23 | ## We Value and Adhere to the Microsoft Open Source Code of Conduct 24 | 25 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 26 | 27 | ## How to use this SDK 28 | 29 | #### Installation 30 | 31 | Add to your Gemfile: 32 | 33 | ```ruby 34 | gem 'omniauth-azure-activedirectory' 35 | ``` 36 | 37 | ### Usage 38 | 39 | If you are already using OmniAuth, adding AzureAD is as simple as adding a new provider to your `OmniAuth::Builder`. The provider requires your AzureAD client id and your AzureAD tenant. 40 | 41 | For example, in Rails you would add this in `config/initializers/omniauth.rb`: 42 | 43 | ```ruby 44 | Rails.application.config.middleware.use OmniAuth::Builder do 45 | provider :azure_activedirectory, ENV['AAD_CLIENT_ID'], ENV['AAD_TENANT'] 46 | # other providers here 47 | end 48 | ``` 49 | 50 | If you are using Sinatra or something else that requires you to configure Rack yourself, you should add this to your `config.ru`: 51 | 52 | ```ruby 53 | use OmniAuth::Builder do 54 | provider :azure_activedirectory, ENV['AAD_CLIENT_ID'], ENV['AAD_TENANT'] 55 | end 56 | ``` 57 | 58 | When you want to authenticate the user, simply redirect them to `/auth/azureactivedirectory`. From there, OmniAuth will takeover. Once the user authenticates (or fails to authenticate), they will be redirected to `/auth/azureactivedirectory/callback` or `/auth/azureactivedirectory/failure`. The authentication result is available in `request.env['omniauth.auth']`. 59 | 60 | If you are supporting multiple OmniAuth providers, you will likely have something like this in your code: 61 | 62 | ```ruby 63 | %w(get post).each do |method| 64 | send(method, '/auth/:provider/callback') do 65 | auth = request.env['omniauth.auth'] 66 | 67 | # Do what you see fit with your newly authenticated user. 68 | 69 | end 70 | end 71 | ``` 72 | 73 | ### Auth Hash 74 | 75 | OmniAuth AzureAD tries to be consistent with the auth hash schema recommended by OmniAuth. [https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema](https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema). 76 | 77 | Here's an example of an authentication hash available in the callback. You can access this hash as `request.env['omniauth.auth']`. 78 | 79 | ``` 80 | :provider => "azureactivedirectory", 81 | :uid => "123456abcdef", 82 | :info => { 83 | :name => "John Smith", 84 | :email => "jsmith@contoso.net", 85 | :first_name => "John", 86 | :last_name => "Smith" 87 | }, 88 | :credentials => { 89 | :code => "ffdsjap9fdjw893-rt2wj8r9r32jnkdsflaofdsa9" 90 | }, 91 | :extra => { 92 | :session_state => '532fgdsgtfera32', 93 | :raw_info => { 94 | :id_token => "fjeri9wqrfe98r23.fdsaf121435rt.f42qfdsaf", 95 | :id_token_claims => { 96 | "aud" => "fdsafdsa-fdsafd-fdsa-sfdasfds", 97 | "iss" => "https://sts.windows.net/fdsafdsa-fdsafdsa/", 98 | "iat" => 53315113, 99 | "nbf" => 53143215, 100 | "exp" => 53425123, 101 | "ver" => "1.0", 102 | "tid" => "5ffdsa2f-dsafds-sda-sds", 103 | "oid" => "fdsafdsaafdsa", 104 | "upn" => "jsmith@contoso.com", 105 | "sub" => "123456abcdef", 106 | "nonce" => "fdsaf342rfdsafdsafsads" 107 | }, 108 | :id_token_header => { 109 | "typ" => "JWT", 110 | "alg" => "RS256", 111 | "x5t" => "fdsafdsafdsafdsa4t4er32", 112 | "kid" => "tjiofpjd8ap9fgdsa44" 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | ## License 119 | 120 | Copyright (c) Microsoft Corporation. Licensed under the MIT License. 121 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # Microsoft Identity SDK Versioning and Servicing FAQ 2 | 3 | We have adopted the semantic versioning flow that is industry standard for OSS projects. It gives the maximum amount of control on what risk you take with what versions. If you know how semantic versioning works with node.js, java, and ruby none of this will be new. 4 | 5 | ##Semantic Versioning and API stability promises 6 | 7 | Microsoft Identity libraries are independent open source libraries that are used by partners both internal and external to Microsoft. As with the rest of Microsoft, we have moved to a rapid iteration model where bugs are fixed daily and new versions are produced as required. To communicate these frequent changes to external partners and customers, we use semantic versioning for all our public Microsoft Identity SDK libraries. This follows the practices of other open source libraries on the internet. This allows us to support our downstream partners which will lock on certain versions for stability purposes, as well as providing for the distribution over NuGet, CocoaPods, and Maven. 8 | 9 | The semantics are: MAJOR.MINOR.PATCH (example 1.1.5) 10 | 11 | We will update our code distributions to use the latest PATCH semantic version number in order to make sure our customers and partners get the latest bug fixes. Downstream partner needs to pull the latest PATCH version. Most partners should try lock on the latest MINOR version number in their builds and accept any updates in the PATCH number. 12 | 13 | Examples: 14 | Using Cocapods, the following in the podfile will take the latest ADALiOS build that is > 1.1 but not 1.2. 15 | ``` 16 | pod 'ADALiOS', '~> 1.1' 17 | ``` 18 | 19 | Using NuGet, this ensures all 1.1.0 to 1.1.x updates are included when building your code, but not 1.2. 20 | 21 | ``` 22 | 26 | ``` 27 | 28 | | Version | Description | Example | 29 | |:-------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------:| 30 | | x.x.x | PATCH version number. Incrementing these numbers is for bug fixes and updates but do not introduce new features. This is used for close partners who build on our platform release (ex. Azure AD Fabric, Office, etc.),In addition, Cocoapods, NuGet, and Maven use this number to deliver the latest release to customers.,This will update frequently (sometimes within the same day),There is no new features, and no regressions or API surface changes. Code will continue to work unless affected by a particular code fix. | ADAL for iOS 1.0.10,(this was a fix for the Storyboard display that was fixed for a specific Office team) | 31 | | x.x | MINOR version numbers. Incrementing these second numbers are for new feature additions that do not impact existing features or introduce regressions. They are purely additive, but may require testing to ensure nothing is impacted.,All x.x.x bug fixes will also roll up in to this number.,There is no regressions or API surface changes. Code will continue to work unless affected by a particular code fix or needs this new feature. | ADAL for iOS 1.1.0,(this added WPJ capability to ADAL, and rolled all the updates from 1.0.0 to 1.0.12) | 32 | | x | MAJOR version numbers. This should be considered a new, supported version of Microsoft Identity SDK and begins the Azure two year support cycle anew. Major new features are introduced and API changes can occur.,This should only be used after a large amount of testing and used only if those features are needed.,We will continue to service MAJOR version numbers with bug fixes up to the two year support cycle. | ADAL for iOS 1.0,(our first official release of ADAL) | 33 | 34 | 35 | 36 | ## Serviceability 37 | 38 | When we release a new MINOR version, the previous MINOR version is abandoned. 39 | 40 | When we release a new MAJOR version, we will continue to apply bug fixes to the existing features in the previous MAJOR version for up to the 2 year support cycle for Azure. 41 | Example: We release ADALiOS 2.0 in the future which supports unified Auth for AAD and MSA. Later, we then have a fix in Conditional Access for ADALiOS. Since that feature exists both in ADALiOS 1.1 and ADALiOS 2.0, we will fix both. It will roll up in a PATCH number for each. Customers that are still locked down on ADALiOS 1.1 will receive the benefit of this fix. 42 | 43 | ## Microsoft Identity SDKs and Azure Active Directory 44 | 45 | Microsoft Identity SDKs major versions will maintain backwards compatibility with Azure Active Directory web services through the support period. This means that the API surface area defined in a MAJOR version will continue to work for 2 years after release. 46 | 47 | We will respond to bugs quickly from our partners and customers submitted through GitHub and through our private alias (tellaad@microsoft.com) for security issues and update the PATCH version number. We will also submit a change summary for each PATCH number. 48 | Occasionally, there will be security bugs or breaking bugs from our partners that will require an immediate fix and a publish of an update to all partners and customers. When this occurs, we will do an emergency roll up to a PATCH version number and update all our distribution methods to the latest. 49 | -------------------------------------------------------------------------------- /spec/omniauth/strategies/azure_activedirectory_spec.rb: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Copyright (c) 2015 Micorosft Corporation 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | #------------------------------------------------------------------------------- 22 | 23 | require 'spec_helper' 24 | require 'omniauth-azure-activedirectory' 25 | 26 | # This was fairly awkward to test. I've stubbed every endpoint and am simulating 27 | # the state of the request. Especially large strings are stored in fixtures. 28 | describe OmniAuth::Strategies::AzureActiveDirectory do 29 | let(:app) { -> _ { [200, {}, ['Hello world.']] } } 30 | let(:x5c) { File.read(File.expand_path('../../../fixtures/x5c.txt', __FILE__)) } 31 | 32 | # These values were used to create the "successful" id_token JWT. 33 | let(:client_id) { 'the client id' } 34 | let(:code) { 'code' } 35 | let(:email) { 'jsmith@contoso.com' } 36 | let(:family_name) { 'smith' } 37 | let(:given_name) { 'John' } 38 | let(:issuer) { 'https://sts.windows.net/bunch-of-random-chars' } 39 | let(:kid) { 'abc123' } 40 | let(:name) { 'John Smith' } 41 | let(:nonce) { 'my nonce' } 42 | let(:session_state) { 'session state' } 43 | let(:auth_endpoint_host) { 'authorize.com' } 44 | 45 | let(:hybrid_flow_params) do 46 | { 'id_token' => id_token, 47 | 'session_state' => session_state, 48 | 'code' => code } 49 | end 50 | 51 | let(:tenant) { 'tenant' } 52 | let(:openid_config_response) { "{\"issuer\":\"#{issuer}\",\"authorization_endpoint\":\"http://#{auth_endpoint_host}\",\"jwks_uri\":\"https://login.windows.net/common/discovery/keys\"}" } 53 | let(:keys_response) { "{\"keys\":[{\"kid\":\"#{kid}\",\"x5c\":[\"#{x5c}\"]}]}" } 54 | 55 | let(:env) { { 'rack.session' => { 'omniauth-azure-activedirectory.nonce' => nonce } } } 56 | 57 | before(:each) do 58 | stub_request(:get, "https://login.windows.net/#{tenant}/.well-known/openid-configuration") 59 | .to_return(status: 200, body: openid_config_response) 60 | stub_request(:get, 'https://login.windows.net/common/discovery/keys') 61 | .to_return(status: 200, body: keys_response) 62 | end 63 | 64 | describe '#callback_phase' do 65 | let(:request) { double('Request', params: hybrid_flow_params, path_info: 'path') } 66 | let(:strategy) do 67 | described_class.new(app, client_id, tenant).tap do |s| 68 | allow(s).to receive(:request) { request } 69 | end 70 | end 71 | 72 | subject { -> { strategy.callback_phase } } 73 | before(:each) { strategy.call!(env) } 74 | 75 | context 'with a successful response' do 76 | # payload: 77 | # { 'iss' => 'https://sts.windows.net/bunch-of-random-chars', 78 | # 'name' => 'John Smith', 79 | # 'aud' => 'the client id', 80 | # 'nonce' => 'my nonce', 81 | # 'email' => 'jsmith@contoso.com', 82 | # 'given_name' => 'John', 83 | # 'family_name' => 'Smith' } 84 | # headers: 85 | # { 'typ' => 'JWT', 86 | # 'alg' => 'RS256', 87 | # 'kid' => 'abc123' } 88 | # 89 | let(:id_token) { File.read(File.expand_path('../../../fixtures/id_token.txt', __FILE__)) } 90 | 91 | # If it passes this test, then the id was successfully validated. 92 | it { is_expected.to_not raise_error } 93 | 94 | describe 'the auth hash' do 95 | before(:each) { strategy.callback_phase } 96 | 97 | subject { env['omniauth.auth'] } 98 | 99 | it 'should contain the name' do 100 | expect(subject.info['name']).to eq name 101 | end 102 | 103 | it 'should contain the first name' do 104 | expect(subject.info['first_name']).to eq given_name 105 | end 106 | 107 | it 'should contain the last name' do 108 | expect(subject.info['last_name']).to eq family_name 109 | end 110 | 111 | it 'should contain the email' do 112 | expect(subject.info['email']).to eq email 113 | end 114 | 115 | it 'should contain the auth code' do 116 | expect(subject.credentials['code']).to eq code 117 | end 118 | 119 | it 'should contain the session state' do 120 | expect(subject.extra['session_state']).to eq session_state 121 | end 122 | end 123 | end 124 | 125 | context 'with an invalid issuer' do 126 | # payload: 127 | # { 'iss' => 'https://sts.imposter.net/bunch-of-random-chars', ... } 128 | # 129 | let(:id_token) { File.read(File.expand_path('../../../fixtures/id_token_bad_issuer.txt', __FILE__)) } 130 | it { is_expected.to raise_error JWT::InvalidIssuerError } 131 | end 132 | 133 | context 'with an invalid audience' do 134 | # payload: 135 | # { 'aud' => 'not the client id', ... } 136 | # 137 | let(:id_token) { File.read(File.expand_path('../../../fixtures/id_token_bad_audience.txt', __FILE__)) } 138 | it { is_expected.to raise_error JWT::InvalidAudError } 139 | end 140 | 141 | context 'with a non-matching nonce' do 142 | # payload: 143 | # { 'nonce' => 'not my nonce', ... } 144 | # 145 | let(:id_token) { File.read(File.expand_path('../../../fixtures/id_token_bad_nonce.txt', __FILE__)) } 146 | it { is_expected.to raise_error JWT::DecodeError } 147 | end 148 | 149 | context 'with the wrong x5c' do 150 | let(:x5c) { File.read(File.expand_path('../../../fixtures/x5c_different.txt', __FILE__)) } 151 | let(:id_token) { File.read(File.expand_path('../../../fixtures/id_token.txt', __FILE__)) } 152 | it { is_expected.to raise_error JWT::VerificationError } 153 | end 154 | 155 | context 'with a non-matching c_hash' do 156 | let(:id_token) { File.read(File.expand_path('../../../fixtures/id_token_bad_chash.txt', __FILE__)) } 157 | it { is_expected.to raise_error JWT::VerificationError } 158 | end 159 | 160 | context 'with a non-matching kid' do 161 | let(:id_token) { File.read(File.expand_path('../../../fixtures/id_token_bad_kid.txt', __FILE__)) } 162 | it { is_expected.to raise_error JWT::VerificationError } 163 | end 164 | 165 | context 'with no alg header' do 166 | let(:id_token) { File.read(File.expand_path('../../../fixtures/id_token_no_alg.txt', __FILE__)) } 167 | 168 | it 'should correctly parse using default RS256' do 169 | expect(subject).to_not raise_error 170 | end 171 | 172 | describe 'the auth hash' do 173 | subject { env['omniauth.auth'] } 174 | before(:each) { strategy.callback_phase } 175 | 176 | it 'should default to RS256' do 177 | expect(subject.info['name']).to eq name 178 | end 179 | end 180 | end 181 | end 182 | 183 | describe '#request_phase' do 184 | let(:strategy) { described_class.new(app, client_id, tenant) } 185 | subject { strategy.request_phase } 186 | before(:each) { strategy.call!(env) } 187 | 188 | it 'should make a redirect' do 189 | expect(subject.first).to eq 302 190 | end 191 | 192 | it 'should redirect to the correct endpoint' do 193 | expect(URI(subject[1]['Location']).host).to eq auth_endpoint_host 194 | end 195 | end 196 | 197 | describe '#read_nonce' do 198 | let(:strategy) { described_class.new(app, client_id, tenant) } 199 | let(:env) { { 'rack.session' => {} } } 200 | before(:each) { strategy.call!(env) } 201 | subject { strategy.send(:read_nonce) } 202 | 203 | context 'before a nonce is set' do 204 | it { is_expected.to be nil } 205 | end 206 | 207 | context 'after a nonce is set' do 208 | before(:each) { @nonce = strategy.send(:new_nonce) } 209 | it 'should match' do 210 | expect(subject).to eq @nonce 211 | end 212 | end 213 | 214 | context 'twice in a row' do 215 | before(:each) do 216 | strategy.send(:new_nonce) 217 | strategy.send(:read_nonce) 218 | end 219 | it { is_expected.to be nil } 220 | end 221 | end 222 | end 223 | -------------------------------------------------------------------------------- /lib/omniauth/strategies/azure_activedirectory.rb: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Copyright (c) 2015 Micorosft Corporation 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | #------------------------------------------------------------------------------- 22 | 23 | require 'jwt' 24 | require 'omniauth' 25 | require 'openssl' 26 | require 'securerandom' 27 | 28 | module OmniAuth 29 | module Strategies 30 | # A strategy for authentication against Azure Active Directory. 31 | class AzureActiveDirectory 32 | include OmniAuth::AzureActiveDirectory 33 | include OmniAuth::Strategy 34 | 35 | class OAuthError < StandardError; end 36 | 37 | ## 38 | # The client id (key) and tenant must be configured when the OmniAuth 39 | # middleware is installed. Example: 40 | # 41 | # require 'omniauth' 42 | # require 'omniauth-azure-activedirectory' 43 | # 44 | # use OmniAuth::Builder do 45 | # provider :azure_activedirectory, ENV['AAD_KEY'], ENV['AAD_TENANT'] 46 | # end 47 | # 48 | args [:client_id, :tenant] 49 | option :client_id, nil 50 | option :tenant, nil 51 | 52 | # Field renaming is an attempt to fit the OmniAuth recommended schema as 53 | # best as possible. 54 | # 55 | # @see https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema 56 | uid { @claims['sub'] } 57 | info do 58 | { name: @claims['name'], 59 | email: @claims['email'] || @claims['upn'], 60 | first_name: @claims['given_name'], 61 | last_name: @claims['family_name'] } 62 | end 63 | credentials { { code: @code } } 64 | extra do 65 | { session_state: @session_state, 66 | raw_info: 67 | { id_token: @id_token, 68 | id_token_claims: @claims, 69 | id_token_header: @header } } 70 | end 71 | 72 | DEFAULT_RESPONSE_TYPE = 'code id_token' 73 | DEFAULT_RESPONSE_MODE = 'form_post' 74 | 75 | ## 76 | # Overridden method from OmniAuth::Strategy. This is the first step in the 77 | # authentication process. 78 | def request_phase 79 | redirect authorize_endpoint_url 80 | end 81 | 82 | ## 83 | # Overridden method from OmniAuth::Strategy. This is the second step in 84 | # the authentication process. It is called after the user enters 85 | # credentials at the authorization endpoint. 86 | def callback_phase 87 | error = request.params['error_reason'] || request.params['error'] 88 | fail(OAuthError, error) if error 89 | @session_state = request.params['session_state'] 90 | @id_token = request.params['id_token'] 91 | @code = request.params['code'] 92 | @claims, @header = validate_and_parse_id_token(@id_token) 93 | validate_chash(@code, @claims, @header) 94 | super 95 | end 96 | 97 | private 98 | 99 | ## 100 | # Constructs a one-time-use authorize_endpoint. This method will use 101 | # a new nonce on each invocation. 102 | # 103 | # @return String 104 | def authorize_endpoint_url 105 | uri = URI(openid_config['authorization_endpoint']) 106 | uri.query = URI.encode_www_form(client_id: client_id, 107 | redirect_uri: callback_url, 108 | response_mode: response_mode, 109 | response_type: response_type, 110 | nonce: new_nonce) 111 | uri.to_s 112 | end 113 | 114 | ## 115 | # The client id of the calling application. This must be configured where 116 | # AzureAD is installed as an OmniAuth strategy. 117 | # 118 | # @return String 119 | def client_id 120 | return options.client_id if options.client_id 121 | fail StandardError, 'No client_id specified in AzureAD configuration.' 122 | end 123 | 124 | ## 125 | # The expected id token issuer taken from the discovery endpoint. 126 | # 127 | # @return String 128 | def issuer 129 | openid_config['issuer'] 130 | end 131 | 132 | ## 133 | # Fetches the current signing keys for Azure AD. Note that there should 134 | # always two available, and that they have a 6 week rollover. 135 | # 136 | # Each key is a hash with the following fields: 137 | # kty, use, kid, x5t, n, e, x5c 138 | # 139 | # @return Array[Hash] 140 | def fetch_signing_keys 141 | response = JSON.parse(Net::HTTP.get(URI(signing_keys_url))) 142 | response['keys'] 143 | rescue JSON::ParserError 144 | raise StandardError, 'Unable to fetch AzureAD signing keys.' 145 | end 146 | 147 | ## 148 | # Fetches the OpenId Connect configuration for the AzureAD tenant. This 149 | # contains several import values, including: 150 | # 151 | # authorization_endpoint 152 | # token_endpoint 153 | # token_endpoint_auth_methods_supported 154 | # jwks_uri 155 | # response_types_supported 156 | # response_modes_supported 157 | # subject_types_supported 158 | # id_token_signing_alg_values_supported 159 | # scopes_supported 160 | # issuer 161 | # claims_supported 162 | # microsoft_multi_refresh_token 163 | # check_session_iframe 164 | # end_session_endpoint 165 | # userinfo_endpoint 166 | # 167 | # @return Hash 168 | def fetch_openid_config 169 | JSON.parse(Net::HTTP.get(URI(openid_config_url))) 170 | rescue JSON::ParserError 171 | raise StandardError, 'Unable to fetch OpenId configuration for ' \ 172 | 'AzureAD tenant.' 173 | end 174 | 175 | ## 176 | # Generates a new nonce for one time use. Stores it in the session so 177 | # multiple users don't share nonces. All nonces should be generated by 178 | # this method. 179 | # 180 | # @return String 181 | def new_nonce 182 | session['omniauth-azure-activedirectory.nonce'] = SecureRandom.uuid 183 | end 184 | 185 | ## 186 | # A memoized version of #fetch_openid_config. 187 | # 188 | # @return Hash 189 | def openid_config 190 | @openid_config ||= fetch_openid_config 191 | end 192 | 193 | ## 194 | # The location of the OpenID configuration for the tenant. 195 | # 196 | # @return String 197 | def openid_config_url 198 | "https://login.windows.net/#{tenant}/.well-known/openid-configuration" 199 | end 200 | 201 | ## 202 | # Returns the most recent nonce for the session and deletes it from the 203 | # session. 204 | # 205 | # @return String 206 | def read_nonce 207 | session.delete('omniauth-azure-activedirectory.nonce') 208 | end 209 | 210 | ## 211 | # The response_type that will be set in the authorization request query 212 | # parameters. Can be overridden by the client, but it shouldn't need to 213 | # be. 214 | # 215 | # @return String 216 | def response_type 217 | options[:response_type] || DEFAULT_RESPONSE_TYPE 218 | end 219 | 220 | ## 221 | # The response_mode that will be set in the authorization request query 222 | # parameters. Can be overridden by the client, but it shouldn't need to 223 | # be. 224 | # 225 | # @return String 226 | def response_mode 227 | options[:response_mode] || DEFAULT_RESPONSE_MODE 228 | end 229 | 230 | ## 231 | # The keys used to sign the id token JWTs. This is just a memoized version 232 | # of #fetch_signing_keys. 233 | # 234 | # @return Array[Hash] 235 | def signing_keys 236 | @signing_keys ||= fetch_signing_keys 237 | end 238 | 239 | ## 240 | # The location of the public keys of the token signer. This is parsed from 241 | # the OpenId config response. 242 | # 243 | # @return String 244 | def signing_keys_url 245 | return openid_config['jwks_uri'] if openid_config.include? 'jwks_uri' 246 | fail StandardError, 'No jwks_uri in OpenId config response.' 247 | end 248 | 249 | ## 250 | # The tenant of the calling application. Note that this must be 251 | # explicitly configured when installing the AzureAD OmniAuth strategy. 252 | # 253 | # @return String 254 | def tenant 255 | return options.tenant if options.tenant 256 | fail StandardError, 'No tenant specified in AzureAD configuration.' 257 | end 258 | 259 | ## 260 | # Verifies the signature of the id token as well as the exp, nbf, iat, 261 | # iss, and aud fields. 262 | # 263 | # See OpenId Connect Core 3.1.3.7 and 3.2.2.11. 264 | # 265 | # @return Claims, Header 266 | def validate_and_parse_id_token(id_token) 267 | # The second parameter is the public key to verify the signature. 268 | # However, that key is overridden by the value of the executed block 269 | # if one is present. 270 | # 271 | # If you're thinking that this looks ugly with the raw nil and boolean, 272 | # see https://github.com/jwt/ruby-jwt/issues/59. 273 | jwt_claims, jwt_header = 274 | JWT.decode(id_token, nil, true, verify_options) do |header| 275 | # There should always be one key from the discovery endpoint that 276 | # matches the id in the JWT header. 277 | x5c = (signing_keys.find do |key| 278 | key['kid'] == header['kid'] 279 | end || {})['x5c'] 280 | if x5c.nil? || x5c.empty? 281 | fail JWT::VerificationError, 282 | 'No keys from key endpoint match the id token' 283 | end 284 | # The key also contains other fields, such as n and e, that are 285 | # redundant. x5c is sufficient to verify the id token. 286 | OpenSSL::X509::Certificate.new(JWT.base64url_decode(x5c.first)).public_key 287 | end 288 | return jwt_claims, jwt_header if jwt_claims['nonce'] == read_nonce 289 | fail JWT::DecodeError, 'Returned nonce did not match.' 290 | end 291 | 292 | ## 293 | # Verifies that the c_hash the id token claims matches the authorization 294 | # code. See OpenId Connect Core 3.3.2.11. 295 | # 296 | # @param String code 297 | # @param Hash claims 298 | # @param Hash header 299 | def validate_chash(code, claims, header) 300 | # This maps RS256 -> sha256, ES384 -> sha384, etc. 301 | algorithm = (header['alg'] || 'RS256').sub(/RS|ES|HS/, 'sha') 302 | full_hash = OpenSSL::Digest.new(algorithm).digest code 303 | c_hash = JWT.base64url_encode full_hash[0..full_hash.length / 2 - 1] 304 | return if c_hash == claims['c_hash'] 305 | fail JWT::VerificationError, 306 | 'c_hash in id token does not match auth code.' 307 | end 308 | 309 | ## 310 | # The options passed to the Ruby JWT library to verify the id token. 311 | # Note that these are not all the checks we perform. Some (like nonce) 312 | # are not handled by the JWT API and are checked manually in 313 | # #validate_and_parse_id_token. 314 | # 315 | # @return Hash 316 | def verify_options 317 | { verify_expiration: true, 318 | verify_not_before: true, 319 | verify_iat: true, 320 | verify_iss: true, 321 | 'iss' => issuer, 322 | verify_aud: true, 323 | 'aud' => client_id } 324 | end 325 | end 326 | end 327 | end 328 | 329 | OmniAuth.config.add_camelization 'azure_activedirectory', 'AzureActiveDirectory' 330 | --------------------------------------------------------------------------------