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
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:
- 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>
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>";
}
- Create signed JWT token in the format specified by MS assertion-format
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;
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();
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;
}
- 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);
}
- 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);
}
}
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";
}
- 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);
}
- 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);
}
}
Top comments (0)