Hot questions for Using Azure in adal4j

Question:

I am trying to turn Azure AD credentials into an OAuth2 authentication code, eventually into a token, so that I can requests to the REST resource management APIs.

Maven Central currently has two artifacts available, com.microsoft.aad/adal and com.microsoft.azure/adal4j. I have downloaded the Javadoc for both and they appear extremely similar, both using the com.microsoft.aad namespace (using adal and adal4j packages in that namespace respectively). They were both released recently and on the same day, so it doesn't appear one is deprecated. They both appear to export similar functionality, but adal4j appears to export significantly less (which makes it look more like a user-facing API, I suppose).

What's the difference between these two packages?


Answer:

Reading the description of both on Maven's site, I believe com.microsoft.aad/adal is Azure Active Directory library for Android apps while com.microsoft.azure/adal4j is Azure Active Directory library for Java Web Apps.

From http://mvnrepository.com/artifact/com.microsoft.aad/adal/1.0.0:

Azure active directory library for Android gives you the ability to add Windows Azure Active Directory authentication to your application with just a few lines of additional code. Using our ADAL SDKs you can quickly and easily extend your existing application to all the employees that use Windows Azure AD and Active Directory on-premises using Active Directory Federation Services, including Office365 customers.

From http://mvnrepository.com/artifact/com.microsoft.azure/adal4j:

Azure active directory library for Java gives you the ability to add Windows Azure Active Directory authentication to your web application with just a few lines of additional code. Using our ADAL SDKs you can quickly and easily extend your existing application to all the employees that use Windows Azure AD and Active Directory on-premises using Active Directory Federation Services, including Office365 customers.

Question:

I am having a hard time trying to authenticate against a particular Tenant ID of Azure. The code that I am using is the following one:

public abstract class Azure
{
    private final static String GRAPH = "https://graph.windows.net/";
    private Logger objLogger;
    private String strAccessToken;
    private String strTenantID;
    private String strLogin;
    private String strAuthorize;
    private String strGraph;
    private String strApplicationID;
    private String strUsername;
    private String strPassword;
    public String getAccessToken() throws InvalidKeyException, MalformedURLException, ServiceUnavailableException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, InterruptedException, ExecutionException
    {
        if (this.strAccessToken == null)
        {
            this.setAccessToken();
        }
        return this.strAccessToken;
    }
    private void setAccessToken() throws MalformedURLException, InterruptedException, ExecutionException, ServiceUnavailableException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException
    {
        AuthenticationContext objContext;
        AuthenticationResult objToken;
        ExecutorService objService;
        Future<AuthenticationResult> objFuture;
        objService = null;
        objToken = null;
        try
        {
            objService = Executors.newFixedThreadPool(1);
            objContext = new AuthenticationContext(this.getAuthorize(), false, objService);
            objFuture = objContext.acquireToken(GRAPH, this.getApplicationID(), this.getUsername(), this.getPassword(), null);
            objToken = objFuture.get();
            this.getLogger().info("Connection to Azure ".concat(this.getClass().getSimpleName().toLowerCase()).concat(" successfully stablished"));
        }
        finally
        {
            objService.shutdown();
        }
        if (objToken == null)
        {
            throw new ServiceUnavailableException("Authentication Service is not available");
        }
        this.strAccessToken = objToken.getAccessToken();
    }
    public void setGraph()
    {
        this.strGraph = GRAPH.concat(this.getTenantID());
    }
}

public class Connection1 extends Azure
{
    private static Connection1 objInstance;
    private Connection1() throws ParameterException, IOException, ParserConfigurationException, SAXException
    {
        super();
        this.setTenantID(<Tenant ID>);
        this.setLogin("https://login.microsoftonline.com/".concat(this.getTenantID()));
        this.setAuthorize(this.getLogin().concat("/oauth2/authorize"));
        this.setGraph();
        this.setApplicationID(<Application ID>);
        this.setAccessToken(null);
        this.setUsername(<username>);
        this.setPassword(<password>);
        this.setLogger();
    }
    public static Azure getInstance() throws ParameterException, IOException, ParserConfigurationException, SAXException
    {
        if (objInstance == null)
        {
            objInstance = new Connection1();
        }
        return objInstance;
    }
}

I have two classes Connection1 and Connection2. Connection2 is a copy of Connection1, the only things that I changed are:

1) Tenant ID

2) Application ID

3) Username

4) Password.

With Connection1 I am able to authenticate and retrieve the data without any issues. The issue comes with Connection2, with this one I get the following error:

[pool-3-thread-1] ERROR com.microsoft.aad.adal4j.AuthenticationContext - [Correlation ID: 63cc6344-2bc1-4f61-aaa0-a2f07acb172b] Execution of class com.microsoft.aad.adal4j.AcquireTokenCallable failed.
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

It seems to be a certificate error, so I research a little online, and they recommend to add "DigiCert Baltimore Root" certificate to my certificate store. The certificate is already there. Do you have any idea of how should I face it?


Answer:

Actually find the issue. I used TamperData addIn of Firefox and check each redirection to get all the sites with their respective certificates. It seems that there was a change in this particular tenant that instead of using DigiCert Baltimore Root it end on Entrust.net Root

Question:

am trying to read user profile/image form Microsoft graphic and am using adal4j-1.5.0.jar to generate the azure token so that based on token i can make a call to graphic API/Microsoft delve.

i am facing issue in below code. it was simple moving to finally block after below line without generating token or any exception. "Future future = context.acquireToken(resourceUri, credential, null);"

String clientId = "clientid";
String clientSecret = "cleintsecret";
String resourceUri = "https://graph.microsoft.com/v1.0/me";

String redirectUri = "http://localhost:9082/contextroot";

String authorityUri ="https://login.microsoftonline.com/{tenent id}/oauth2/authorize";


AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
    service = Executors.newFixedThreadPool(1);
    context = new AuthenticationContext(authorityUri, false, service);
    ClientCredential credential = new ClientCredential(clientId,clientSecret);

    Future<AuthenticationResult> future = context.acquireToken(resourceUri, credential, null);



    result = future.get();
}
finally {
    service.shutdown();
}

Answer:

It seems that some dependencies of adal4j-1.5.0 are not download as well, please check the jar package files from your project. Based on my test if I use the adal4j 1.5.0. I found that are some dependencies of adal4j-1.5.0 are missing from the project. Then I can't get the access token.

But If I use the adal4j 1.0.0, it works correctly for me. If the version 1.0.0 is acceptable, you could use that as a workaround or add the dependencies manually.

Test demo code:

 private static final String APP_ID = "clientId";
 private static final String APP_SECRET = "secret key";
 private static final String TENATID = "xxxxx";
 public static void main(String[] args) throws Exception {
 String authority = "https://login.microsoftonline.com/"+TENATID; 
 String resourceUrl = "https://graph.microsoft.com"; //Microsoft graph. AD graph: https://graph.windows.net
 ExecutorService service = Executors.newFixedThreadPool(1);
 AuthenticationContext context = new AuthenticationContext(authority, true, service);
        // Acquire Token
 Future<AuthenticationResult> result = context.acquireToken(
                resourceUrl,
                new ClientCredential(APP_ID, APP_SECRET),
                null
        );
        String token = result.get().getAccessToken();
        System.out.println(token);
    }

Question:

I am connecting to Azure AD enabled API using java back-end server. I am able to get the Access Token by following java code.

    String tenantId = "************";
    String username = "***************";
    String password = "*************";
    String clientId = "**********";
    String resource = "***********";
    String userEmail = "**********";


    AuthenticationContext authContext = null;
    AuthenticationResult authResult = null;
    ExecutorService service = null;

    try
    {
        service = Executors.newFixedThreadPool( 1 );
        String url = "https://login.microsoftonline.com/" + tenantId + "/oauth2/authorize";
        authContext = new AuthenticationContext( url, false, service );
        Future<AuthenticationResult> future = authContext.acquireToken(
                resource,
                clientId,
                userEmail,
                password,
                null );

        authResult = future.get();

    }
    catch( Exception ex )
    {
        ex.printStackTrace();
    }

Please note that API provider is is not supporting client credentials currently.

The issue for me is, using the refresh token received in above code to get a new access token.

ADAL4j java library doesn't seems to have any methods supporting this. A Documentation for java library

But in .NET library there are methods like,

public AuthenticationResult AcquireTokenByRefreshToken(
string refreshToken,
string clientId,
string resource
)

For refreshing access token without any credential provided.

Why these methods are not provided in Java library?. Are there any restrictions? And what are possible workarounds?

Thanks in advance.


Answer:

As far as I know, although the Java ADAL4J library doesn't support the method

public AuthenticationResult AcquireTokenByRefreshToken(
string refreshToken,
string clientId,
string resource
)

which is supported in .Net library, both of the two types of libraries are implemented via HTTP REST API.

You could refer to the Refreshing the access tokens in the official document

// Line breaks for legibility only

POST /{tenant}/oauth2/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&refresh_token=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq...
&grant_type=refresh_token
&resource=https%3A%2F%2Fservice.contoso.com%2F
&client_secret=JqQX2PNo9bpM0uEihUPzyrh    // NOTE: Only required for web apps

I use Postman to test a request for acquiring accessToken by refreshToken without credentials for your reference:

Corresponded, I implemented the request with the following Java code

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationResult;

public class AcquireTokenByRefreshToken {

    static String tenantId = "***";
    static String username = "***";
    static String password = "***";
    static String clientId = "***";
    static String resource = "https://graph.windows.net";
    static String userEmail = "***";

    public static void main(String[] args) throws MalformedURLException, IOException {
        AuthenticationContext authContext = null;
        AuthenticationResult authResult = null;
        ExecutorService service = null;

        try {
            service = Executors.newFixedThreadPool(1);
            String url = "https://login.microsoftonline.com/" + tenantId + "/oauth2/authorize";
            authContext = new AuthenticationContext(url, false, service);
            Future<AuthenticationResult> future = authContext.acquireToken(resource, clientId, userEmail, password,
                    null);

            authResult = future.get();
            System.out.println("get access token: \n" + authResult.getAccessToken());
            System.out.println("get refresh token: \n" + authResult.getRefreshToken());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        // get access token by refresh token
        getToken(authResult.getRefreshToken());
    }

    public static void getToken(String refreshToken) throws IOException {

        String encoding = "UTF-8";
        String params = "client_id=" + clientId + "&refresh_token=" + refreshToken
                + "&grant_type=refresh_token&resource=https%3A%2F%2Fgraph.windows.net";
        String path = "https://login.microsoftonline.com/" + tenantId + "/oauth2/token";
        byte[] data = params.getBytes(encoding);
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        conn.setRequestProperty("Content-Length", String.valueOf(data.length));
        conn.setConnectTimeout(5 * 1000);
        OutputStream outStream = conn.getOutputStream();
        outStream.write(data);
        outStream.flush();
        outStream.close();
        System.out.println(conn.getResponseCode());
        System.out.println(conn.getResponseMessage());

        BufferedReader br = null;
        if (conn.getResponseCode() != 200) {
            br = new BufferedReader(new InputStreamReader((conn.getErrorStream())));
        } else {
            br = new BufferedReader(new InputStreamReader((conn.getInputStream())));
        }
        System.out.println("Response body : " + br.readLine());
    }

}

The result printed in console as below :

Hope it helps you.