Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Office 365 Rest API - Daemon week authentication

I am trying to build a Ruby Daemon service to access the Office 365 rest API. It was recently made possible to do this via the OAuth 'client_credentials' flow, as detailed in this blog post: https://learn.microsoft.com/en-us/archive/blogs/exchangedev/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow

I am struggling to generate a valid access token. The token endpoint returns me a JWT however when using this token I received a 401 with this message:

The access token is acquired using an authentication method that is too weak to allow access for this application. Presented auth strength was 1, required is 2

I understand that the client_credentials flow requires you to present a X.509 cert, unfortunately all the examples in the blog post are for C#.

I am using a generated self signed cert and private key to do a client assertion when requesting the token. I followed the steps in the blog post to generate the cert and update the manifest to use this cert.

This is the ruby code for reference:

def request_token
  uri = URI.parse("https://login.windows.net/== TENANT-ID ==/oauth2/token?api-version=1.0")
  https = Net::HTTP.new(uri.host, uri.port)

  req = Net::HTTP::Post.new(uri.request_uri)
  req.set_form_data(
    :grant_type    => 'client_credentials',
    :redirect_uri  => 'http://spready.dev',
    :resource      => 'https://outlook.office365.com/',
    :client_id     => '== Client ID ==',
    :client_secret => '== Client secret =='
  )

  https.use_ssl = true
  https.cert = client_cert
  https.key = client_key
  https.verify_mode = OpenSSL::SSL::VERIFY_PEER

  resp = https.start { |cx| cx.request(req) }

  @access_token = JSON.parse(resp.body)
end

Obviously I have removed certain bits of information for security. Even though it is ruby you can see I am using my cert to validate the client using an SSL connection.

Here's some more infomation on the error:

"x-ms-diagnostics" => "2000010;
    reason=\"The access token is acquired using an authentication method that is too weak to allow access for this application. Presented auth strength was 1, required is 2.\";
    error_category=\"insufficient_auth_strength\"", 
"x-diaginfo"=>"AM3PR01MB0662", 
"x-beserver"=>"AM3PR01MB0662"

Any help would be appreciate.


Edit

For others looking to do something similar in Ruby here's a Gist of the code I use: https://gist.github.com/NGMarmaduke/a088943edbe4e703129d

The example uses a Rails environment but it should be fairly easy to strip out the Rails specific bits.

Remember to replace YOUR CLIENT ID, TENANT_ID and CERT_THUMBPRINT with the correct values and point the cert path and client key methods to the right file path.

Then you can do something like this:

mailbox = OfficeAPI.new("[email protected]")
messages = mailbox.request_messages
like image 746
Nick Maher Avatar asked Feb 05 '15 18:02

Nick Maher


3 Answers

I just managed to get this working, so I thought I'd throw one more piece of advice into the mix. All the instruction articles out there say that you should add your certificate to the manifest file. I had trouble with that, but here is what I did that finally made it work:

  • In Azure, go to Settings > Management Certificates
  • Upload the public key as a .cer file (google around if you don't know how to convert it). This should be a binary file that your text editor barfs on.
  • Now that it's uploaded, Microsoft will give you the thumbprint. It's in the "Thumbprint" column. But, it's in hex, not base64. So, convert it like this:

    # Hint: use your actual thumbprint, not this fake one
    echo '5292850026FADB09700E7D6C1BCB1CD1F3270BCC' | xxd -r -p | base64
    
  • Finally, use this base64 encoded thumbprint as the value for x5t in the JSON header.

like image 103
Andrew Thaddeus Martin Avatar answered Oct 21 '22 23:10

Andrew Thaddeus Martin


Instead of a client_secret in your request body, you need a client_assertion. This is a bit more complex, but it's the reason you need that certificate.

Basically you need to build a JSON Web Token and sign it with your certificate using a SHA256 hash. The token is going to look something like this:

Header:

{ 
  "alg": "RS256",
  "x5t": "..." // THUMBPRINT of Cert
}

Payload:

{
  "aud": "https:\\/\\/login.windows.net\\/<The logged in user's tenant ID>\\/oauth2\\/token",
  "exp": 1423168488,
  "iss": "YOUR CLIENT ID",
  "jti": "SOME GUID YOU ASSIGN",
  "nbf": 1423167888,
  "sub": "YOUR CLIENT ID"
}

If you're still with me, you now need to base64-encode both pieces (separately), then concatenate them with a '.'. So now you should have:

base64_header.base64_payload

Now you take that string and sign it with your certificate, using a SHA256 hash. Then base64-encode the result of that, url-encode it, then append to the string, so now you have:

base64_header.base64_payload.base64_signature

Finally, include this in your POST to the token endpoint as the client_assertion parameter, and also include a client_assertion_type parameter set to "urn:ietf:params:oauth:client-assertion-type:jwt-bearer":

req.set_form_data(
    :grant_type    => 'client_credentials',
    :redirect_uri  => 'http://spready.dev',
    :resource      => 'https://outlook.office365.com/',
    :client_id     => '== Client ID ==',
    :client_assertion_type => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
    :client_assertion => 'base64_header.base64_payload.base64_signature'
  )

I hope that helps! This is all based on my research into how ADAL does it, and I haven't tested it myself in Ruby.

like image 38
Jason Johnston Avatar answered Oct 21 '22 23:10

Jason Johnston


I added a function in HomeController on the git to demo how to request an access token by hand using client assertion w/o ADAL. It might be easier to port using this: https://github.com/mattleib/o365api-as-apponly-webapp/commit/12d5b6dc66055625683020576139f5771e6059e1

like image 1
Matthias Leibmann Avatar answered Oct 21 '22 23:10

Matthias Leibmann