Hot questions for Using Azure in authentication

Top Java Programmings / Azure / authentication

Question:

I'm trying to implement a spring-boot based REST service that should use Azure AD as an OAuth2 server for client authentication.

I registered two applicatons:

  1. Mobile native app that is using as a client for my service
  2. Rest-service as a backend.

All requests to the backend app should be authenticated through Azure AD with using OAuth2 flow.

As an implementation of mobile app I'm using curl:

For obtaining a Bearer token I use https://login.microsoftonline.com/TENANT_ID/oauth2/token

curl -s -X POST https://login.microsoftonline.com/<TENANT_ID>/oauth2/token -d grant_type=password -d username=$USER_NAME -d password=$PASSWORD -d resource=$RESOURCE_ID -d client_id=$CLIENT_ID

where $USER_NAME and $PASSWORD are credetials of an Azure AD user, $RESOURCE_ID is a SID of my REST service and $CLIENT_ID is a SID of my mobile client for the REST serice.

Azure successfully returns JSON with token data.

My Oauth2 Config for Backend app:

@Configuration
@EnableResourceServer
public class OAuth2Config extends ResourceServerConfigurerAdapter {
 @Bean
    ResourceServerTokenServices resourceTokenServices() {
        RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setClientId(resourceId);
        tokenServices.setClientSecret(/*I do not have it*/resourcePassword);
        tokenServices.setCheckTokenEndpointUrl(/*I do not have it*/checkToken);
        return tokenServices;
    }

@Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenServices(resourceTokenServices());
        resources.resourceId("rest_api");
    }

@Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").authenticated();
    }
}

My REST controller:

@RestController
@RequestMapping("/data")
public class CustomerRestController {

    @RequestMapping(method = RequestMethod.GET)
    public SomeData getMyData(Principal principal){
        System.out.println("RESOURCE WAS REQUESTED BY " + principal.getName());
        return new SomeData(principal.getName());
    }
}

But I didn't find in the endpoint list any URL that can be used by my REST service for checking a bearer token and obtaining user data from Azure AD. Also, as I understand, it should be present some kind of credentials for my REST service for using Azure AD

How can I find required values or I'm going by a wrong way?


Answer:

Finally I've got an answer.

Azure AD uses JWT tokens for authorization, so I have to implement work with this type of tokens instead of checking a token on the server.

Question:

I've been trying for a week to authenticate on Azure Active Directory with a Java application which presents client credential grant, in order to retrieve an access token to target the Outlook Office365 REST API, but could not succeed. The server always returns an error :

com.microsoft.aad.adal4j.AuthenticationException: {"error":"unauthorized_client","error_description":"AADSTS70002: Error validating credentials. AADSTS50064: Credential validation failed

I have followed the MSDN blog post http://blogs.msdn.com/b/exchangedev/archive/2015/01/21/building-demon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow.aspx and uploaded my public cert in the application manifest on Azure.

I'm using the Java Library ADAL4J in version 0.0.4 with the following code :

        service = Executors.newFixedThreadPool(1);

        final KeyStore keystore = KeyStore.getInstance("PKCS12", "SunJSSE");
        keystore.load(new FileInputStream(MyApp.class.getResource(TestConfiguration.AAD_CERTIFICATE_PATH).getFile()),
                        TestConfiguration.AAD_CERTIFICATE_PASSWORD.toCharArray());
        final String alias = keystore.aliases().nextElement();
        final PrivateKey key = (PrivateKey) keystore.getKey(alias, TestConfiguration.AAD_CERTIFICATE_PASSWORD.toCharArray());
        final X509Certificate cert = (X509Certificate) keystore.getCertificate(alias);
        final AsymmetricKeyCredential asymmetricKeyCredential = AsymmetricKeyCredential.create(TestConfiguration.AAD_CLIENT_ID, key, cert);
        ctx = new AuthenticationContext(TestConfiguration.AAD_TENANT_ENDPOINT, false, service);
        final Future<AuthenticationResult> result = ctx.acquireToken(TestConfiguration.AAD_RESOURCE_ID, asymmetricKeyCredential,null);
        ar = result.get();

Here is my TestConfiguration object :

public final class TestConfiguration {

public final static String AAD_HOST_NAME = "login.windows.net";
public final static String AAD_TENANT_NAME = "MYTENANT.onmicrosoft.com"; 
public final static String AAD_TENANT_ENDPOINT = "https://" + AAD_HOST_NAME + "/" + AAD_TENANT_NAME + "/";
public final static String AAD_CLIENT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
public final static String AAD_CLIENT_SECRET = "xxxxxxxxxx";
public final static String AAD_RESOURCE_ID = "https://outlook.office365.com/"; 
public final static String AAD_CERTIFICATE_PATH = "/MyCert.pfx";
public final static String AAD_CERTIFICATE_PASSWORD = "xxxxxxx";
}

Here is the com.microsoft.aad.adal4j.AdalOAuthRequest request object properties produced :

authorization : null
contentType : application/x-www-form-urlencoded; charset=UTF-8
extraHeaderParams : {client-request-id=d942e921-71d7-47ef-9f97-29e8bb4122aa, x-client-OS=Windows 7, x-client-SKU=java, return-client-request-id=true, x-client-CPU=amd64, x-client-VER=1.0}
method : POST
query : client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&grant_type=client_credentials&client_assertion=eyJhbGciOiJSUzI1NiIsIn ... cut ... G0U4MeBV69EGauQ&resource=https%3A%2F%2Foutlook.office365.com%2F
url : https://login.windows.net/mytenant.onmicrosoft.com/oauth2/token

Where may have I missed some thing ?

Thanks for your help.

P.S. : when I download my application manifest back from Azure, I find my "keyCredentials" JSON object but it's "value" property is null (whereas I did previously put my public cert in it) ... Does it mean it is really null or just an empty property for security reasons ??


Answer:

commons-codec version needed to be updated from 1.4 to 1.5 to make the encoding work correctly. http://commons.apache.org/proper/commons-codec/changes-report.html#a1.5

EDIT FROM ERIC

I finally managed to retrieve an access token by upgrading my common-codecs artifact from 1.4 (with which it was not working) to 1.5 .

We got thinking of the common-codecs artifact because the JWT token presented had a public certificate value with "\r\n" inserted when generated by my app, whereas it hadn't when it was generated from a dedicated sample test app.

Those "\r\n" characters were inserted into the "x5c" JWT header property by a call to org.apache.commons.codec.binary.Base64.encodeBase64String(byte[]) from com.microsoft.aad.adal4j.AsymmetricKeyCredential.getPublicCertificate().

It appeared that my app had already a depency to common-codecs v1.4 which didn't suit to adal4j.

Question:

I managed to get Azure AD Authentication to work by using the sample apps from ADAL4J library from GitHub.

Here it is working.

My question is: Can the same result be accomplished without the redirect to the Microsoft page? The customer wants to use his own internal login page.

I'm thinking some sort of behind the scenes POST request to the same URL, and getting the reply. This means my app will see the user credentials, I am ok with that.

Is this supported by Azure AD?

Environment: Java server side (Spring), AngularJS UI.

Thanks.


Answer:

The redirect is necessary. Only AAD should be collecting AAD credentials. See this answer for more information on why that is:

How to authenticate user with Azure Active Directory using OAuth 2.0?

Question:

I am using 1.0.0-beta4.1 Azure Java SDK. This is my code for authentication

    // TODO Auto-generated method stub
    String client = "xxxxxxxxxxx";
    String tenant = "xxxxxxxxxxx";
    String key = "xxxxxxxxxxx";
    String subscriptionId = "xxxxxxxxxxx";

    ApplicationTokenCredentials credentials = new ApplicationTokenCredentials(client, tenant, key, AzureEnvironment.AZURE);
    Azure azure = Azure.authenticate(credentials).withSubscription(subscriptionId);
    System.out.println("Listing all resource groups");

The code doesn't throw any error in case of incorrect credentials. Is there any way to find whether the authentication was successful or not.


Answer:

According to your code, it seems that missing some required methods which include configure(), please see below.

Azure azure = Azure.configure()  // Initial an Azure.Configurable object
                   .withLogLevel(HttpLoggingInterceptor.Level.BASIC)
                   .authenticate(credentials)
                   .withSubscription(subscriptionId);

Please try to use the code above instead of yours. Any update, please feel free to let me know.

Question:

I was following this tutorial https://dev.outlook.com/restapi/tutorial/java in order to walk through the process of creating a simple Java Spring MVC app that retrieves messages in Office 365 or Outlook.com.

What I did so far:

  • Registered Our Application on AZURE-365-MAIL-API Registration
  • Used appId, appPassword, and redirectUrl in to my application and made a request.

Here the controller class:

@RestController
@RequestMapping("/auth")
public class AuthorizeController {

@RequestMapping(value = "/authorize", method = RequestMethod.GET)
public JasonMessage authorize(
        @RequestParam("code") String code,
        @RequestParam("id_token") String idToken,
        @RequestParam("state") UUID state,
        HttpServletRequest request) {
    {
        // Get the expected state value from the session
        HttpSession session = request.getSession();
        UUID expectedState = (UUID) session.getAttribute("expected_state");
        UUID expectedNonce = (UUID) session.getAttribute("expected_nonce");

        // Make sure that the state query parameter returned matches
        // the expected state
        if (state.equals(expectedState)) {
            session.setAttribute("authCode", code);
            session.setAttribute("idToken", idToken);
        } else {
            session.setAttribute("error", "Unexpected state returned from authority.");
        }

        JasonMessage jasonMessage= new JasonMessage();
        jasonMessage.setStatus("success");
        jasonMessage.setData("id_token",idToken);
        jasonMessage.setData("code",code);
        jasonMessage.setData("state",state);
        return jasonMessage;
    }

}

}

Here is also the entry point:

@RestController
@RequestMapping("/office365")
public class IndexController {

@RequestMapping(value = "/service/mail",
        method = RequestMethod.GET)
public void Office365(Model model, HttpServletRequest request, HttpServletResponse response) {
    UUID state = UUID.randomUUID();
    UUID nonce = UUID.randomUUID();

    // Save the state and nonce in the session so we can
    // verify after the auth process redirects back
    HttpSession session = request.getSession();
    session.setAttribute("expected_state", state);
    session.setAttribute("expected_nonce", nonce);

    String loginUrl = AuthHelper.getLoginUrl(state, nonce);
    model.addAttribute("loginUrl", loginUrl);




    try {
         response.sendRedirect(loginUrl);
    } catch (IOException e) {
        e.printStackTrace();

    }
}



public class AuthHelper {


private static final String authority = "https://login.microsoftonline.com";
private static final String authorizeUrl = authority + "/common/oauth2/v2.0/authorize";

private static String[] scopes = {
        "openid",
        "offline_access",
        "profile",
        "https://outlook.office.com/mail.read"
};

private static String appId = "9489e4b5-875d-4bd7-924b-88b3b562ccc7";
private static String appPassword = "0uPnh7gJi86eSWWwr6E2M3F";
private static String redirectUrl = "http://localhost:8080/controller/auth/authorize";

private static String getAppId() {
    if (appId == null) {
        try {
            loadConfig();
        } catch (Exception e) {
            return null;
        }
    }
    return appId;
}
private static String getAppPassword() {
    if (appPassword == null) {
        try {
            loadConfig();
        } catch (Exception e) {
            return null;
        }
    }
    return appPassword;
}

private static String getRedirectUrl() {
    if (redirectUrl == null) {
        try {
            loadConfig();
        } catch (Exception e) {
            return null;
        }
    }
    return redirectUrl;
}

private static String getScopes() {
    StringBuilder sb = new StringBuilder();
    for (String scope: scopes) {
        sb.append(scope + " ");
    }
    return sb.toString().trim();
}

private static void loadConfig() throws IOException {
    String authConfigFile = "auth.properties";
    InputStream authConfigStream = AuthHelper.class.getClassLoader().getResourceAsStream(authConfigFile);

    if (authConfigStream != null) {
        Properties authProps = new Properties();
        try {
            authProps.load(authConfigStream);
            appId = authProps.getProperty("appId");
            appPassword = authProps.getProperty("appPassword");
            redirectUrl = authProps.getProperty("redirectUrl");
        } finally {
            authConfigStream.close();
        }
    }
    else {
        throw new FileNotFoundException("Property file '" + authConfigFile + "' not found in the classpath.");
    }
}

public static String getLoginUrl(UUID state, UUID nonce) {

    UriComponentsBuilder urlBuilder = UriComponentsBuilder.fromHttpUrl(authorizeUrl);
    urlBuilder.queryParam("client_id", getAppId());
    urlBuilder.queryParam("redirect_uri", getRedirectUrl());
    urlBuilder.queryParam("response_type", "code id_token");
    urlBuilder.queryParam("scope", getScopes());
    urlBuilder.queryParam("state", state);
    urlBuilder.queryParam("nonce", nonce);
    urlBuilder.queryParam("response_mode", "form_post");

    return urlBuilder.toUriString();
}

}

Entry URL: localhost:8080/controller/office365/service/mail I believe the problem is with our redirect url which is http://localhost:8080/controller/auth/authorize .

This is the error: The reply address 'http://localhost:8080/controller/auth/authorize' isn't using a secure scheme.**

Our application requires authentication so before I use the entry url, I manually login to our application and then hit the entry url. Do I need to put the reply url in a way it won't require authentication ? If that is the case I can simply modify web.xml and create a class to by pass authentication. If that is not the problem, I would appreciate your help.

I've also tried using HTTPS but it caused another error.

Thank you!


Answer:

Azure will not redirect from an authorization request to a non-HTTPS URL. Localhost is the only exception. You'll need to secure your site with HTTPS and make sure that the redirect you give it is HTTPS.

Question:

trying to connect with azure AuthenticationResult ,through rest service call but getting error saying

{ "code": 500, "message": "java.lang.NoClassDefFoundError: com/nimbusds/jwt/JWTParser" }

private static AuthenticationResult getAccessTokenFromUserCredentials(String username, String password)
        throws Exception {
    AuthenticationContext context;
    AuthenticationResult result;
    ExecutorService service = null;
    try {
        service = Executors.newFixedThreadPool(1);
        context = new AuthenticationContext(AUTHORITY, false, service);
        Future<AuthenticationResult> future = context.acquireToken("https://graph.microsoft.com", CLIENT_ID, username, password, null);

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

    if (result == null) {
        throw new ServiceUnavailableException("authentication result was null");
    }
    return result;
}

Answer:

I have got similar error. Its due to runtime dependency class un availability. Try adding this nimbus-jose-jwt JAR Version 4.2.

The link is https://jar-download.com/artifacts/com.nimbusds/nimbus-jose-jwt/4.2/source-code

The way to find out this error is, search with your class name com.nimbusds.jwt.JWTParser jar and you must be finding that class in the jar before adding it. Try it and tell us.

Question:

I am using java to make a REST Api call to Azure, put an object to its storage. I did this successfully last week but now its now working for some reasons. The error message is as below:

<?xml version="1.0" encoding="utf-8" ?>
<Error>
<Code>AuthenticationFailed</Code>
<Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:a15f2626-0001-004f-778c-f34383000000 Time:2017-07-02T23:43:20.2826278Z</Message>
<AuthenticationErrorDetail>The Date header in the request is incorrect.</AuthenticationErrorDetail>
 </Error>

Answer:

You could refer to the code below if you haven't solved this Authentication error:

public class PutTest {

    private static final String account = <your account name>;
    private static final String key = <your account key>;

    public static void main(String args[]) throws Exception {

        File file = new File(<your file path>);
        FileInputStream inputStream = new FileInputStream(<your file path>);

        String urlString = "https://" + account + ".blob.core.windows.net/<your container>/<your file name e.g:test.txt>";
        HttpURLConnection connection = (HttpURLConnection) (new URL(urlString)).openConnection();
        getFileRequest(connection, account, key,  file.length());
        // connection.connect();
        connection.setDoInput(true);
        connection.setDoOutput(true);

        byte[] buffer = new byte[inputStream.available()];
        inputStream.read(buffer);
        OutputStream out = connection.getOutputStream();
        out.write(buffer);
        out.flush();
        out.close();
        System.out.println("Response message : " + connection.getResponseMessage());
        System.out.println("Response code : " + connection.getResponseCode());

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

    public static void getFileRequest(HttpURLConnection request, String account, String key, long length)
            throws Exception {
        SimpleDateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss");
        fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = fmt.format(Calendar.getInstance().getTime()) + " GMT";
        String stringToSign = "PUT\n" + "\n" // content encoding
                + "\n" // content language
                +length+"\n"// content length
                + "\n" // content md5
                +"\n" // content type
                + "\n" // date
                + "\n" // if modified since
                + "\n" // if match
                + "\n" // if none match
                + "\n" // if unmodified since
                + "\n" // range
                + "x-ms-blob-type:BlockBlob" + "\n" 
                + "x-ms-date:" + date + "\n"
                + "x-ms-version:2015-02-21"+"\n" // headers
                + "/" + account + request.getURL().getPath(); // resources
        System.out.println("stringToSign : " + stringToSign);
        String auth = getAuthenticationString(stringToSign);
        request.setRequestMethod("PUT");

        request.setRequestProperty("x-ms-blob-type", "BlockBlob");
        request.setRequestProperty("x-ms-date", date);
        request.setRequestProperty("x-ms-version", "2015-02-21");
        request.setRequestProperty("Authorization", auth);
        request.setRequestProperty("Content-Length", String.valueOf(length));

    }

    private static String getAuthenticationString(String stringToSign) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(Base64.decode(key), "HmacSHA256"));
        String authKey = new String(Base64.encode(mac.doFinal(stringToSign.getBytes("UTF-8"))));
        String auth = "SharedKey " + account + ":" + authKey;
        return auth;
    }
}

Hope it helps.

Question:

I have an Azure webapp (App Service) running with Tomcat. I'd deployed 2 war applications. WAR-1 provides web service call which return a json files using Springboot. WAR-2 is a web application which call this web services in WAR-1. This webapp has system assigned managed identity (or MSI). In addition, this webapp has authentication on with AAD, using Express configuration.

I can access static pages in WAR-2, after authentication through AAD. Now I need to fetch data from WAR-1. I have a servlet which contains code like this:

String subscriptionId = "xxxx";
String testURL    = "https://yyy.azurewebsites.net/war1/person/100";
String resourceId = "https://management.azure.com/";

AppServiceMSICredentials credentials = new AppServiceMSICredentials(AzureEnvironment.AZURE);
Azure azure = Azure.configure()
                .withLogLevel(LogLevel.BODY_AND_HEADERS)
                .authenticate(credentials)
                .withSubscription(subscriptionId);
String token = credentials.getToken(resourceId);
HttpURLConnection conn = (HttpURLConnection) new URL(testURL).openConnection();
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + token);

int responseCode = conn.getResponseCode();

OutputStream os = conn.getOutputStream();
....

I do able to get a token, but the response code is 500 when I make the GET call.

So my question is ... is this the correct way to do this call ? I did found an articlehttps://dotnetdevlife.wordpress.com/2018/10/22/call-azure-ad-protected-website-using-managed-service-identity-msi/ similar to this situation but it uses .Net. I cannot find any Java equivalent of this.


Answer:

I tested at my side, and here are my steps:

1. Two apps in one Azure web app.

App1: https://jackdemoapp1.azurewebsites.net/app1/

App2: https://jackdemoapp1.azurewebsites.net/app2/

2. Configure Authentication/Authorization on Azure portal.

And you can get the client ID by clicking into the details, note it down and we will use it in app2:

3. Configure managed identity on Azure portal

To simplify the test, the app1 will just return a "Hello" string.

4. Code in app2
    @ResponseBody
    @RequestMapping("/")
    public String index() {

        JSONObject json = new JSONObject();

        try {
            AppServiceMSICredentials credential = new AppServiceMSICredentials(AzureEnvironment.AZURE);
            // As we want to get token for accessing the aad-protected app, change the
            // resource to the client ID you get in step 2
            String token = credential.getToken("ac07d701-6f7d-462e-8b67-5dffa1df955f");
            json.put("token", token);

            // The URL for app1 API
            String app1 = "https://jackdemoapp1.azurewebsites.net/app1/";
            HttpURLConnection conn = (HttpURLConnection) new URL(app1).openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Authorization", "Bearer " + token);
            conn.setDoOutput(true);
            conn.setDoInput(true);

            // Open the connection
            conn.connect();

            int code = conn.getResponseCode();
            if (code >= 200 && code <= 300) {
                try (InputStream inputStream = conn.getInputStream();
                        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                        BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
                    StringBuilder stringBuilder = new StringBuilder();
                    String line = "";

                    while ((line = bufferedReader.readLine()) != null) {
                        stringBuilder.append(line);
                    }

                    String response = stringBuilder.toString();
                    json.put("response", response);
                }
            } else {
                json.put("Error", "Response Code" + conn.getResponseCode());
            }
            conn.disconnect();

        } catch (Exception e) {
            json.put("Exception", e.getStackTrace());
        }
        return json.toString();
    }

Result

Question:

I'm working on onderive for business and require client credential flow for accessing it through java. can anyone provide me authentication example for the above mentioned source so that i can work on it!

I'm trying to follow this flow, but i was missing some context which left me with partial work.

I successfully created certificate and uploaded to manifest. But couldn't get any further. could you please help me on the process of how to authenticate based on this X.509 certificate.


Answer:

It seems that you need to Use a SAML 2.0 identity provider to implement single sign-on.

But I think that using OAuth2 for authentication is the recommended way.

There is a sample project for OneDrive in Java on GitHub that I think you can refer to and use the url of OneDrive for Business instead of the url for OneDrive.

For example, to get an authorization code, using the url for OneDrive for Business https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id={client_id}&redirect_uri={redirect_uri} instead of the url for OneDrive https://login.live.com/oauth20_authorize.srf?client_id={client_id}&scope={scope}&response_type=code&redirect_uri={redirect_uri}.

In the sample project, the OneDriveAuthorisationProvider.java implemented the authentication process that you can change the code to be compatible with OneDrive for Business.

As references, please see the docments below.

  1. OneDrive authentication and sign-in https://dev.onedrive.com/auth/msa_oauth.htm
  2. OneDrive for Business authentication and sign in https://dev.onedrive.com/auth/aad_oauth.htm

Hope it helps.