Last active
August 6, 2025 12:54
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # | |
| # 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