Skip to content

Instantly share code, notes, and snippets.

@larskanis
Last active August 6, 2025 12:54
Show Gist options
  • Save larskanis/d23290db049827149705256206d08caf to your computer and use it in GitHub Desktop.
Save larskanis/d23290db049827149705256206d08caf to your computer and use it in GitHub Desktop.
This is how to access the online service of Microsoft Office 365 by IMAP protocol in Ruby. It uses OAUTH2 authentication through the browser.
#
# This is how to access the online service of Microsoft Office 365 by IMAP protocol in Ruby.
#
# It uses OAUTH2 authentication through the browser.
# The authentication is done with the Thunderbird client_id, so that it should work equally to your Thunderbird access.
#
# Adjust your mail address:
email_address = '[email protected]'
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'net-imap', '~> 0.5'
gem 'oauth2', '~> 2.0'
gem 'openssl'
end
require "rubygems/gemcutter_utilities"
def create_cert(name)
key = OpenSSL::PKey::RSA.new 2048
cert = OpenSSL::X509::Certificate.new
cert.serial = 0
cert.version = 2
cert.not_before = Time.now
cert.not_after = Time.now + 86400
cert.public_key = key.public_key
cert_name = OpenSSL::X509::Name.parse "CN=#{name}/DC=example"
cert.subject = cert_name
extension_factory = OpenSSL::X509::ExtensionFactory.new nil, cert
cert.issuer = cert_name
extension_factory.issuer_certificate = cert
# build a CA cert
# This extension indicates the CA’s key may be used as a CA.
cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)
# This extension indicates the CA’s key may be used to verify signatures on both certificates and certificate revocations.
cert.add_extension extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true)
cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
# Root CA certificates are self-signed.
cert.sign(key, OpenSSL::Digest::SHA256.new)
[cert, key]
end
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
# Several retries because of the self-signed local SSL certificate
retries = 3
begin
auth_code = Gem::GemcutterUtilities::WebauthnListener.new(logon_site).wait_for_otp_code(sserver)
rescue OpenSSL::SSL::SSLError => err
retry if (retries-=1) > 0
raise
end
# 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", ...]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment