Pages

Wednesday, May 1, 2013

Office 365 OAuth 2.0 Test Client Setup


OAuth 2.0 Assertion Flow with Office 365 Authorization Server

Pr-Requisite Software
  •  Download Gradle Software
 Gradle All
  •  Setup Environment Variable and Path
           Define GRADLE_HOME Directory
           Setup the PATH for gradle. For Example
           Set GRADLE_HOME=c:\Gradle-1.5
           Set PATH= %GRADLE_HOME%\bin;%PATH%
           Set the Java Home Directory. For example set JAVA_HOME="c:\Progra~1\Java\jdk1.6.0_31".
  •  Creating the New App Principal Name

Build the Gradle Build File (build.gradle)
    apply plugin : 'java'
    apply plugin : 'eclipse'
    apply plugin : 'application'
    sourceCompatibility = 1.6
    group = 'oauth.sample.office365.client'
    version = '1.0'
    repositories {
          mavenCentral()
    }
   
    dependencies{
                compile 'org.apache.httpcomponents:httpclient:4.2.2','com.google.code.gson:gson:2.2.2','org.slf4j:log4j-over-slf4j:1.7.5'
    }


Create a Eclipse Project Directory
       Open a Command Prompt and create a following directory structure in c drive in windows or else unix. For unix use forward slash instead of back slash
       mkdir oauth
       mkdir oauth\office365oauth
       mkdir oauth\office365oauth\src
       mkdir oauth\office365oauth\src\main
       mkdir oauth\office365oauth\src\main\java
       mkdir oauth\office365oauth\src\main\resources

      Copy the gradle.build file to c:\oauth\office365oauth
      Execute the gradle cleanEclipse eclipse command and it will create the eclipse project.                 
Import the Project into the Eclipse
         Open th eclipse. Click File --> Import --> General --> Existing Project into Workspace --> Next. It will display the Following Screen.

'
 Click Brose Button And Choose the Eclipse Project Directory. In Our Case C:\oauth\Office365oauth project

 After Choosing the Eclipse Project, Please Click Ok Button and it will display the following screen.

 Click Finish button and It will Import the eclipse project into the workspace. The sample imported eclipse screen shot given below.



       
Build the Test Client for Oauth 2.0
Build the OAuth Test Client Devided into 2 parts.  They are
 a. Build Assertion
 b. Send Request to Authorization Server to Get The Access Token
                
 Build Assertion

 Assertion need to be encoded with base64 format. The following sequence build the assertion. They are
   
Building Assertion Header
  The assertion header parameters are typ and alg. The typ is Header type is JWT (JSON Web Token) and alg is algorithm is HS256 (Hash Sha 256). The Sample Code is given below.

  Map <String,String> header= new HashMap<String,String>()
  header.put("typ","JWT");
  header.put("alg","HS256");
     
Convert the Assertion Header to JSON Object            

 String strHeader==gson.toJson(header);

Convert the JSON Object to Encode Base 64 Object
String strEnodeHeader=Base64.encodeBase64String(strHeader.getBytes("UTF8"));
Assertion Header Completed. Next Build the Assertion Pay Load.
Build Assertion Pay Load
 The assertion payload parameters are iss, aud, nbf, and exp. 
 The iss is issuer. The issuer is the application principal id @ Microsoft Office 365 Tenant.
 The aud is audience and the format of the aud is URI. The Sample aud is ACS Principal ID / Authorization   Host @  Microsoft Office 365 Tenant. 
 The nbf is Not Before. The sample value is Current Date in seconds.
 The exp is expired. The expiration of Access Token is in seconds.
 The Sample code of the PayLoad is given below.

   Map<String,String> payload= new HashMap<String,String>();
   payload.put("iss",AppPrincipalName+"@"+"Microsoft Office 365 Tenant Name");
   payload.put("aud",ACSPrincipalID+"/"+"Authorizer Server Host"+"@"+"Microsoft Office 365 Tenant Name");
   payload.put("nbf",""+(new Date().getTime()/1000));
   payload.put("exp",""+(addSecondsToCurrentTime(3600).getTime()/1000));

Convert the PayLoad Object to JSon Object
  
  String strPayLoad= gson.toJson(payload);
Encode PayLoad JSON Obectwith Base 64
 String strEnodepayLoad=Base64.encodeBase64String(strPayLoad.getBytes("UTF8"));
Assertion Pay Load Completed. Next Combine the Assertion Header Encode with Assertion Payload Encode

Append Assertion Header Encode with Assertion Payload Encode
 As per the JWT specification the assertion encode header and assertion payload header append with dot. The Code is given below.

 String rawToken=  strEnodeHeader + "." +strPayLoad;

Sign the Raw Token                 

As per the the JWT specification, the raw token need to be signed with HmacSHA256 with secret key of the Application user Principal Name. This secret key is generated while creating the application principal name using the power shell. The sample Code is given below.

SecretKeySpec secretKey = null;

secretKey = new SecretKeySpec(Base64.decodeBase64(signingKey), "HmacSHA256");
Mac mac;

byte[] signedData = null;

try{

  mac = Mac.getInstance("HmacSHA256");
  mac.init(secretKey);
  mac.update(rawToken.getBytes("UTF-8"));
  signedData = mac.doFinal();

}

 catch (Exception e) {
            e.printStackTrace();

}
       

Build The Signature

As per the JWT specification, the signed token needs to be encoded with base 64. The sample Code is given below

 String signature = Base64.encodeBase64String(signedData);

Encode Signatre and RawToken

As per the JWT specification the raw token and signature append with dot and encode with base64 format. The Code is given below.

 String assertion = String.format("%s.%s", rawToken, signature);

Assertion Building Completed.

Send Request to Authorization Server to Get The Access Token

 Build the Post Request

  The Authorization server url is required to construct the pst request. The sample code is given below.

DefaultHttpClient client= new DefaultHttpClient();
HttpPost post= new HttpPost("https://accounts.accesscontrol.windows.net/tokens/OAuth/2");
List <NameValuePair> nvps = new ArrayList<NameValuePair>();

 Add the Request Parameters to the Post Request
 The resource format is uri and Protected Resource Principal Id is 00000002-0000-0000-c000-000000000000 / Protected Resource Host Name is graph.windows.net @ Microsoft Office 365 Tenant.

 String resource=String.format("%s/%s@%s", "00000002-0000-0000-c000-000000000000", "graph.windows.net","Microsoft Tenant Name");
 nvps.add(new BasicNameValuePair("grant_type", "http://oauth.net/grant_type/jwt/1.0/bearer"));
 nvps.add(new BasicNameValuePair("assertion", assertion));
 nvps.add(new BasicNameValuePair("resource", resource));


Send Post Request

post.setEntity(new UrlEncodedFormEntity(nvps,"UTF-8"));
HttpResponse response=client.execute(post); 


Extract the Response


if(response.getStatusLine().getStatusCode() == 200){

  HttpEntity entity=response.getEntity();
  if (entity != null)

 { 
   String retresponse=handler.handleResponse(response);
   The response format is JSON and response attributes are token_type, access_token, expires_in, not_before, expires_on, resource
 
 }     
  
Example Code

package oauth.sample.office365.client;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import oauth.sample.office365.OauthToken;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import com.google.gson.Gson;

public class TestOAuthClient 
{
    private static String ALGORITHM="HS256";
    private static String TYPE="JWT";
    private static String APP_PRINCIPAL_ID="<Replace with App Service Principal ID>";
    private static String ACS_PRINCIPAL_ID="00000001-0000-0000-c000-000000000000";
    private static String AUTHRIZER_SERVER_HOST="accounts.accesscontrol.windows.net";

   private static String DOMAIN_CONTEXT_ID="Replace with MicroSoft Office 365 Tenant Name";
    private static String SYMMETRIC_KEY="Replace with Secret Key";
    private static String AUTHRIZER_SERVER_URL="https://accounts.accesscontrol.windows.net/tokens/OAuth/2";
    private static String GRANT_TYPE_URL="http://oauth.net/grant_type/jwt/1.0/bearer";
    private static String PROTECTED_RESOURCE_PRINCIPAL_ID="00000002-0000-0000-c000-000000000000";
    private static String PROTECTED_RES_HOST_NAME="graph.windows.net";
    
    private static Gson gson= new Gson();
    public TestOAuthClient() {
        // TODO Auto-generated constructor stub
    }
    
    public static void main(String args[]) throws Exception
    {
        
        String assertion=buildAssertion();
        
        // Initializing the Http Connection
        DefaultHttpClient client= new DefaultHttpClient();
        HttpPost post= new HttpPost(AUTHRIZER_SERVER_URL);
        List <NameValuePair> nvps = new ArrayList<NameValuePair>();
        
        String resource=String.format("%s/%s@%s", PROTECTED_RESOURCE_PRINCIPAL_ID, PROTECTED_RES_HOST_NAME,DOMAIN_CONTEXT_ID);
        
        nvps.add(new BasicNameValuePair("grant_type", GRANT_TYPE_URL));
        nvps.add(new BasicNameValuePair("assertion", assertion));
        nvps.add(new BasicNameValuePair("resource", resource));
        
        post.setEntity(new UrlEncodedFormEntity(nvps,"UTF-8"));
        
        HttpResponse response=client.execute(post);
        
        System.out.println("----------------------------------------");
        System.out.println(response.getStatusLine());
        System.out.println(response.getProtocolVersion());

        if(response.getStatusLine().getStatusCode() == 200)
        {
            diplayHeaders(response,"authenticate");
            HttpEntity entity=response.getEntity();
            if (entity != null) {
                
                System.out.println("Response content length: " + entity.getContentLength());
                BasicResponseHandler handler= new BasicResponseHandler();
                String retresponse=handler.handleResponse(response);
                
                OauthToken token =gson.fromJson(retresponse, OauthToken.class);
                
                System.out.println(retresponse);
                
                System.out.println("Exire On " +token.getExpires_on());
                System.out.println("Exire in " +token.getExpires_in());
                System.out.println("Exire not Before " +new Date(token.getNot_before()*1000));
                System.out.println("Current Time " +new Date().getTime());
                
                System.out.println("Expire Date "+ addSecondsToCurrentTime((int)token.getExpires_in()) + "  - "+token.getExpires_on());
                
                
                EntityUtils.consume(entity);
            }
        }
        else
        {
            diplayHeaders(response,"authenticate");
          
            //throw new Office365Exception("Authentication Token Generation Error "+config.getAuthUrl());
        }
    }
    
    public static void diplayHeaders(HttpResponse response,String methodName)
    {
        Header headers[]= response.getAllHeaders();
        for (Header header : headers) 
        {
            System.out.println(methodName+ " - Headers  - "+header.getName() + "  -  "+header.getValue());
        }
    }

    
    public static String buildAssertion()
    {
        String result="";
        // start Assertion
        
        
        // Building Header
        
        Map<String, String> headerMap= new HashMap<String, String>();
        headerMap.put("alg", ALGORITHM);
        headerMap.put("typ", TYPE);
        
        String strHeader=buildStringFromMap(headerMap);
        System.out.println(strHeader);
        
        Map<String, String> body= new HashMap<String,String>();
        //format App Principal ID @ Domain Context ID
        
        body.put("iss", APP_PRINCIPAL_ID+"@"+DOMAIN_CONTEXT_ID);
        // Format ACS Principal Name / Authentication Host @ Domain Context ID 
        body.put("aud", ACS_PRINCIPAL_ID+"/"+AUTHRIZER_SERVER_HOST+"@"+DOMAIN_CONTEXT_ID);
        body.put("nbf", ""+(new Date().getTime()/1000));
        body.put("exp", ""+(addSecondsToCurrentTime(3600).getTime()/1000));
        String strBody=buildStringFromMap(body);
        System.out.println(strBody);
        // Building Raw Token header + "." + Body
        String rawToken= strHeader + "."+ strBody;
        
        // sign the data using symmetric Key
        
        byte signedBytes[]= signData(SYMMETRIC_KEY, rawToken);
        
        //Create the Signature
        
        String signature = Base64.encodeBase64String(signedBytes);
        
        // Build the Access Token  format is rawToken +"."+signature
        
        result = String.format("%s.%s", rawToken, signature);
        
        // End Assertion
        
        
        
        System.out.println(strHeader);
        
        
        
        return result;
    }
    

    private static byte[] signData(String signingKey, String rawToken)  
    {
        SecretKeySpec secretKey = null;
        System.out.println("Finding Decode Key "+new String(Base64.decodeBase64(signingKey)));
        secretKey = new SecretKeySpec(Base64.decodeBase64(signingKey), "HmacSHA256");
        Mac mac;
        byte[] signedData = null;
        try
        {
            mac = Mac.getInstance("HmacSHA256");
            mac.init(secretKey);
            mac.update(rawToken.getBytes("UTF-8"));
            signedData = mac.doFinal();
        } catch (Exception e) {
            e.printStackTrace();
        }        
        return signedData;
    }
    
    
    public static String buildStringFromMap(Map<String, String> data)
    {
        String result="";
        if(!data.isEmpty())
        {
            result=gson.toJson(data);
            try {
                result=Base64.encodeBase64String(result.getBytes("UTF8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    public static Date addSecondsToCurrentTime(int seconds) 
    {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
        System.out.println(calendar.getTime());
        calendar.add(Calendar.SECOND, seconds);
        
        return calendar.getTime();
    }

}






package oauth.sample.office365;

public class OauthToken 
{
    private String token_type;
    private String access_token;
    private long expires_in;
    private long not_before;
    private long expires_on;
    private String resource;
    
    
    public String getToken_type() {
        return token_type;
    }


    public void setToken_type(String token_type) {
        this.token_type = token_type;
    }


    public String getAccess_token() {
        return access_token;
    }


    public void setAccess_token(String access_token) {
        this.access_token = access_token;
    }


    public long getExpires_in() {
        return expires_in;
    }


    public void setExpires_in(long expires_in) {
        this.expires_in = expires_in;
    }


    public long getNot_before() {
        return not_before;
    }


    public void setNot_before(long not_before) {
        this.not_before = not_before;
    }


    public long getExpires_on() {
        return expires_on;
    }


    public void setExpires_on(long expires_on) {
        this.expires_on = expires_on;
    }


    public String getResource() {
        return resource;
    }


    public void setResource(String resource) {
        this.resource = resource;
    }


    public OauthToken() {
        // TODO Auto-generated constructor stub
    }

}
 



Compile the Code 

 Go to the Eclipse Project Directory command Prompt and run the gradle build command and It will compile. The compilation output as follows


C:\oauth\office365oauth>gradle build
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build UP-TO-DATE

BUILD SUCCESSFUL



Execute the Test Client
Add the following content at the end of the line in the build.gradle file.

mainClassName="oauth.sample.office365.client.TestOAuthClient"


After Adding the above content , the build file shown like this.



apply plugin : 'java'
apply plugin : 'eclipse'
apply plugin : 'application'

sourceCompatibility = 1.6
group = 'oauth.sample.office365.client'
version = '1.0'
repositories {
 mavenCentral()
}
dependencies{
    compile "org.apache.httpcomponents:httpclient:4.2.2",
    "com.google.code.gson:gson:2.2.2",
    "org.slf4j:log4j-over-slf4j:1.7.5"
}
mainClassName="oauth.sample.office365.client.TestOAuthClient" 



After running the gradle run command the following output is shown.


C:\oauth\office365oauth>gradle run
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Wed May 01 18:33:17 PDT 2013
eyJleHAiOiIxMzY3NDYxOTk3IiwibmJmIjoiMTM2NzQ1ODM5NyIsImF1ZCI6IjAwMDAwMDAxLTAwMDAtMDAwMC1jMDAwLTAwMDAwMDAwMDAwMC9hY2NvdW50cy5hY2Nlc3Njb250cm9s
LndpbmRvd3MubmV0QHNmc3VvZmZpY2UzNjVkZXYub25taWNyb3NvZnQuY29tIiwiaXNzIjoiNGU5MGNmNDItNmM2MC00NzA2LTg1MDktZTM5YzM3NjViYzExQHNmc3VvZmZpY2UzNjVk
ZXYub25taWNyb3NvZnQuY29tIn0=
Finding Decode Key ò╟P↔°?Θ╣■:/£ôm┼zÖ6*9?←≥≥Q§←££0
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
----------------------------------------
HTTP/1.1 200 OK
HTTP/1.1
authenticate - Headers  - Cache-Control  -  no-cache, no-store
authenticate - Headers  - Pragma  -  no-cache
authenticate - Headers  - Content-Type  -  application/json; charset=utf-8
authenticate - Headers  - Expires  -  -1
authenticate - Headers  - request-id  -  9ee87462-e08d-4ad2-af7a-2c50f47b6346
authenticate - Headers  - X-Content-Type-Options  -  nosniff
authenticate - Headers  - Date  -  Thu, 02 May 2013 01:34:14 GMT
authenticate - Headers  - Content-Length  -  1218
Response content length: 1218
{"token_type":"Bearer","access_token":"Access Token with Encoded string","expires_in":"43200","not_before":"1367458455","expires_on":"1367501655","resour
ce":"00000002-0000-0000-c000-000000000000/graph.windows.net@Microsoft Tenant ID"}
Exire On 1367501655
Exire in 43200
Exire not Before Wed May 01 18:34:15 PDT 2013
Current Time 1367458399228
Wed May 01 18:33:19 PDT 2013
Expire Date Thu May 02 06:33:19 PDT 2013  - 1367501655

BUILD SUCCESSFUL
 

 References:

 JWT Specification 
 Bearer Token Specification
 Assertion Flow Specification 


2 comments:

  1. I have try this code and having problem with this code !

    ReplyDelete
    Replies
    1. what error your getting. please post the error and i will look the error and get back to you

      Delete