Reputation: 313
So MS disabled IMAP for basic auth as we all know.
I am trying to figure out how to get the OAUTH 2.0 working using ruby (not ruby on rails). I have Azure APP and everything needed (I think), but I can not find any code related to ruby and getting the access token.
First step is completed, but next step is to get the access token. https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
I need to read different Outlook mailboxes.
Could someone please explain how to do this?
Upvotes: 3
Views: 2396
Reputation: 896
The online service of Microsoft Office 365 can be accessed by the IMAP protocol in Ruby with OAuth2.
At first the access should be checked with Thunderbird. If the permission is granted by the organization to use Thunderbird, then the following example should login too. It uses OAuth2 authentication through the browser similar to Thunderbird.
The full example is here in this gist, but the essence is this:
logon_site = 'https://login.microsoftonline.com'
# client_id and empty client_secret of Thunderbird
client_id = '9e5f94bc-e8a4-4e73-b8be-63364c29d753'
client_secret = ''
# Connect to the OAuth2 logon server
client = OAuth2::Client.new(client_id, client_secret, site: logon_site, authorize_url: "/common/oauth2/authorize", token_url: "/common/oauth2/token")
# Start a local web server with self-signed SSL certs
context = OpenSSL::SSL::SSLContext.new
context.cert, context.key = create_cert("localhost")
server = TCPServer.new(0)
sserver = OpenSSL::SSL::SSLServer.new(server, context)
port = server.addr[1].to_s
redirect_uri = "https://localhost:#{port}"
# Build and show the URI with the authentication request
auth_url = client.auth_code.authorize_url(redirect_uri: redirect_uri)
puts "Please visit the following URI for authentication.\nYou need to accept the invalid certificate warning.\n #{auth_url}"
# Wait until the auth_code was received from the browser on the local web server
auth_code = Gem::WebauthnListener.wait_for_otp_code(logon_site, sserver)
# Request an access token for IMAP
access = client.auth_code.get_token(auth_code, redirect_uri: redirect_uri, resource: 'https://outlook.office.com', client_id: client_id)
# Connect to the IMAP port
imap = Net::IMAP.new('outlook.office365.com', 993, true)
imap.authenticate('XOAUTH2', email_address, access.token)
# List all imap folders
pp imap.list("", "*").map(&:name) # => ["INBOX", "Sent", "Trash", ...]
Upvotes: 0
Reputation: 313
SOLUTION for me!
Steps I took.
Device Flow
" requests.Device Authorization Request
(you need a scope and client_id for this) I used https://outlook.office.com/IMAP.AccessAsUser.All
scope.Device Access Token Request
and use the "device_code" from the last request and put that under code
, under body.access_token
Connect using ruby
require 'gmail_xoauth' # MUST HAVE! otherwise XOAUTH2 auth wont work
require 'net/imap'
imap = Net::IMAP.new(HOST, PORT, true)
access_token = "XXXXX"
user_name = "[email protected]"
p imap.authenticate('XOAUTH2',"#{user_name}", "#{access_token}")
# example
imap.list('','*').each do |folders|
p folders
end
XOAUTH2 Returns
#<struct Net::IMAP::TaggedResponse tag="RUBY0001", name="OK", data=#<struct Net::IMAP::ResponseText code=nil, text="AUTHENTICATE completed.">, raw_data="RUBY0001 OK AUTHENTICATE completed.\r\n
Just to specify
HOST = 'outlook.office365.com'
PORT = 993
UPDATE 25.01.2023
class Oauth2
require 'selenium-webdriver'
require 'webdrivers'
require 'net/http'
# Use: Oauth2.new.get_access_code
# Grants access to Office 365 emails.
def get_access_code
p "### Access Request Started #{Time.now} ###"
begin
codes = device_auth_request
authorize_device_code(codes[:user_code])
access_code = device_access_token(codes[:device_code])
access_code
rescue => e
p e
p "Something went wrong with authorizing"
end
end
def device_auth_request # Returns user_code and device_code
url = URI('https://login.microsoftonline.com/organizations/oauth2/v2.0/devicecode')
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Post.new(url)
request.body = "client_id=YOUR_CLIENT_ID&scope=%09https%3A%2F%2Foutlook.office.com%2FIMAP.AccessAsUser.All"
response = https.request(request)
{
user_code: JSON.parse(response.read_body)["user_code"],
device_code: JSON.parse(response.read_body)["device_code"]
}
end
def device_access_token(device_code)
url = URI('https://login.microsoftonline.com/organizations/oauth2/v2.0/token')
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Post.new(url)
request.body = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&code=#{device_code}&client_id=YOUR_CLIENT_ID"
response = https.request(request)
JSON.parse(response.read_body)["access_token"]
end
def authorize_device_code(device_code)
# SELENIUM SETUP
driver = setup_selenium
driver.get "https://microsoft.com/devicelogin"
sleep(4)
# ------------------------------------------
# Give Access
element = driver.find_element(:class, "form-control")
element.send_keys(device_code)
sleep(2)
element = driver.find_element(:id, "idSIButton9")
element.submit
sleep(2)
element = driver.find_element(:id, "i0116")
element.send_keys("YOUR OUTLOOK ACCOUNT EMAIL")
sleep(2)
element = driver.find_element(:class, "button_primary")
element.click
sleep(2)
element = driver.find_element(:id, "i0118")
element.send_keys("YOUR OUTLOOK PASSWORD")
element = driver.find_element(:class, "button_primary")
element.click
sleep(2)
element = driver.find_element(:class, "button_primary")
element.click
sleep(2)
# ------------------------------------------
driver.quit
end
def setup_selenium
require 'selenium-webdriver'
# set up Selenium
options = Selenium::WebDriver::Chrome::Options.new(
prefs: {
download: {
prompt_for_download: false
},
plugins: {
'always_open_pdf_externally' => true
}
}
)
options.add_argument('--headless')
options.add_argument('--no-sandbox')
# options.add_argument('-incognito')
options.add_argument('disable-popup-blocking')
Selenium::WebDriver.for :chrome, options: options
end
end
Upvotes: 5