MS Graph API Certificate and Client Secret OAuth2.0 in Java Spring boot

Brundha S V - Nov 3 - - Dev Community

Overview

Microsoft Graph API is a gateway to data and service management in Microsoft 365. An access token is required to call MS Graph APIs. In this article we learn how to obtain OAuth access token with a certificate or a secret. This approach is best suited for Admin-Consent Apps that needs access without a user, more info here.

Code Overview

Access Full code here

GitHub logo brundhasv / MSGraphclientCreds

MS Graph API Certificate and Client credentials OAuth2.0 in Java Spring boot

MS Graph API Certificate and Client credentials OAuth2.0 in Java Spring boot

Overview

Microsoft Graph API is a gateway to data and service management in Microsoft 365. An access token is required to call MS Graph APIs. This repo can be used obtain OAuth access token with a certificate or a secret. This approach is best suited for Admin-Consent Apps that needs access without a user, more info here.

Usage

    String MS_TENANT_ID = "<YOUR_MS_TENANT_ID_HERE>";

    /** 1. Fetch token using certificate */
    AuthResponse response = AuthClient.fetchNewToken(MS_TENANT_ID,"cert");
    log.info("Token Response using Certificate : {}",response);

    /** 2. Fetch token using secret */
    response = AuthClient.fetchNewToken(MS_TENANT_ID,"secret");
    log.info("Token Response using Secret : {}",response);

Explanation of Code here:

https://dev.to/brundhasv/ms-graph-api-certificate-and-client-credentials-authentication-in-java-spring-boot-3hc7




  • Language used - Java Springboot
  • Libraries - security library nimbus and bouncycastle
  • Project - Maven

pom.xml

<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>oauth2-oidc-sdk</artifactId>
    <version>11.10</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk18on</artifactId>
    <version>1.76</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Access token request with a Certificate

OAuth2 Client credentials flow is supported by MS Graph API to obtain access token using certificate. Certificate Auth is recommended over to secret Auth.
Prerequisite- Your certificate needs to be uploaded under your 'App Registration>Certificates & Secrets'. You can also use self-signed certificate if deployed in local environment.
Reference- Link

Code Explanation

Use MS token URL and MS App id and uploaded certificate to obtain oauth2 access token.

  • Populate MS OAuth Token URL, scope and your Entra App id and certificate and its key path in Constants.java. PEM certificate is used in this example. Certificate key is needed to sign JWT Token.
public class Constants {
    //ms app client/app id
    public static final String MS_APP_CLIENT_ID = "<YOUR_MS_APPLICATION_ID_HERE>";

    //ms graph api oauth token url
    public static final String MS_APP_TOKEN_URL =
            "https://login.microsoftonline.com/%s/oauth2/v2.0/token";

    //ms graph api oauth token url
    public static final String MS_APP_TOKEN_SCOPE = "https://graph.microsoft.com/.default";

    //client certificate details
    public static final String CERT_PATH = "<YOUR_CERT_PATH_HERE>";
    public static final String CERT_KEY_PATH = "<YOUR_CERT_KEY_PATH_HERE>";
}
Enter fullscreen mode Exit fullscreen mode

JWTHeader
Create JWT header using public key from certificate. PEM type cert is used in this example.

X509Certificate cert = getPublicKeyFromCert(Constants.CERT_PATH);
String thumbprint = getThumbprint(cert);
Map<String, Object> jwsMap = new HashMap<>();
jwsMap.put("alg", HEADER_ALG);
jwsMap.put("typ", HEADER_TYP);
jwsMap.put("x5t", thumbprint);
JWSHeader jwsHeader = JWSHeader.parse(jwsMap);
log.debug("JWT header - {}", jwsHeader.toJSONObject());
return jwsHeader;
Enter fullscreen mode Exit fullscreen mode

JWTClaims
Certificate Claims is built as specified by MS, setting expiration for 10mins and issuer/subject as App client id.

LocalDateTime localDate = LocalDateTime.now();
Date dateNow = Date.from(localDate.atZone(ZoneId.systemDefault()).toInstant());
LocalDateTime localDateExp = localDate.plus(Duration.ofMinutes(JWT_EXPIRY_MINS));
Date dateExp = Date.from(localDateExp.atZone(ZoneId.systemDefault()).toInstant());
UUID uuid = UUID.randomUUID();
JWTClaimsSet jwtClaims =
new JWTClaimsSet.Builder()
        .audience(tokenUrl)
        .issueTime(dateNow)
        .notBeforeTime(dateNow)
        .expirationTime(dateExp)
        .jwtID(uuid.toString())
        .issuer(Constants.MS_APP_CLIENT_ID)
        .subject(Constants.MS_APP_CLIENT_ID)
        .build();
Enter fullscreen mode Exit fullscreen mode

SignedJWT
Using above created JWT header and Claims, create JWT token and finally sign with certificate private key.

 private static SignedJWT createSignedJWT(String tokenUrl) {
    JWSHeader jwsHeader = createJWTHeader();
    JWTClaimsSet jwtClaims = createJWTClaims(tokenUrl);
    SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaims);
    RSAPrivateKey certJWK = readPKCS8PrivateKey(Constants.CERT_KEY_PATH);
    try {
        signedJWT.sign(new RSASSASigner(certJWK));
    } catch (Exception e) {
        throw new AuthException("Error while signing JWT", e.toString());
    }
    log.debug("JWT Cert Token for MS App - {}", signedJWT.serialize());
    return signedJWT;
}
Enter fullscreen mode Exit fullscreen mode
  • Use above created signed JWT to make OAuth request using nimbus library 'PrivateKeyJWT'.
/** Access token request with a certificate */
private static TokenRequest getClientCertTokenRequest(String tenantId)
        throws URISyntaxException {
    String tokenUrl = String.format(Constants.MS_APP_TOKEN_URL, tenantId);
    SignedJWT signedJWT = getSignedJWT(tokenUrl);
    ClientAuthentication clientAuth = new PrivateKeyJWT(signedJWT);
    AuthorizationGrant clientGrant = new ClientCredentialsGrant();
    Scope scope = new Scope(Constants.MS_APP_TOKEN_SCOPE);
    URI tokenEndpoint = new URI(tokenUrl);

    // Make the token request
    return new TokenRequest(tokenEndpoint, clientAuth, clientGrant, scope);
}    
Enter fullscreen mode Exit fullscreen mode
  • Then with your Org/App Tenant Id, fetch token using above method. Tenant Id is MS account Id for your org/domain, this can be found in your Entra App.
@Component
@Slf4j
public class AuthCLI implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        String MS_TENANT_ID = "<YOUR_MS_TENANT_ID_HERE>";

        /** 1. Fetch token using certificate */
        AuthResponse response = AuthClient.fetchNewToken(MS_TENANT_ID,"cert");
        log.info("Token Response using Certificate : {}",response);
    }
}
Enter fullscreen mode Exit fullscreen mode

Access token request with a Secret

OAuth2 Client credentials flow is supported by MS Graph API to obtain access token using secret.
Prerequisite- You should generate client secret under your 'App Registration>Certificates & Secrets' and note down. This is recommended for test/setup in local environment.
Reference- Link

Code Explanation

Use MS token URL and MS App id and generated secret to obtain oauth2 access token.

  • Populate MS OAuth Token URL, scope and your Entra App id and generated secret in Constants.java
public class Constants {
    //ms app client/app id
    public static final String MS_APP_CLIENT_ID = "<YOUR_MS_APPLICATION_ID_HERE>";

    //client secret
    public static String MS_APP_CLIENT_SECRET = "<YOUR_MS_CLIENT_SECRET_HERE>";

    //ms graph api oauth token url
    public static final String MS_APP_TOKEN_URL =
            "https://login.microsoftonline.com/%s/oauth2/v2.0/token";

    //ms graph api oauth token url
    public static final String MS_APP_TOKEN_SCOPE = "https://graph.microsoft.com/.default";
}
Enter fullscreen mode Exit fullscreen mode
  • Use nimbus library 'ClientSecretBasic' to make OAuth 2-legged Token request using above parameters.
/** Access token request with a secret */
private static TokenRequest getClientCredsTokenRequest(String tenantId)
        throws URISyntaxException {
    AuthorizationGrant clientGrant = new ClientCredentialsGrant();

    // The credentials to authenticate the client at the token endpoint
    ClientID clientID = new ClientID(Constants.MS_APP_CLIENT_ID);
    Secret clientSecret = new Secret(Constants.MS_APP_CLIENT_SECRET);
    ClientAuthentication clientAuth = new ClientSecretBasic(clientID, clientSecret);

    Scope scope = new Scope(Constants.MS_APP_TOKEN_SCOPE);
    String tokenUrl = String.format(Constants.MS_APP_TOKEN_URL, tenantId);
    URI tokenEndpoint = new URI(tokenUrl);

    // Make the token request
    return new TokenRequest(tokenEndpoint, clientAuth, clientGrant, scope);
}    
Enter fullscreen mode Exit fullscreen mode
  • Then with your Org/App Tenant Id, fetch token using above method. Tenant Id is MS account Id for your org/domain, this can be found in your Entra App.
@Component
@Slf4j
public class AuthCLI implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        String MS_TENANT_ID = "<YOUR_MS_TENANT_ID_HERE>";

        /** 2. Fetch token using secret */
        response = AuthClient.fetchNewToken(MS_TENANT_ID,"secret");
        log.info("Token Response using Secret : {}",response);
    }
}
Enter fullscreen mode Exit fullscreen mode
. .