I am trying to use microsoft graph api and I need authorization code for using that.
Redirecting the application to microsoft login site is not possible in my application.
I need to call this and for that I require authProvider:
IGraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider)
.buildClient();
I am using this for creating authProvider:
UsernamePasswordProvider authProvider = new UsernamePasswordProvider(CLIENT_ID,
Arrays.asList("https://graph.microsoft.com/user.read", "https://graph.microsoft.com/Mail.ReadWrite",
"https://graph.microsoft.com/Calendars.ReadWrite"),
USERNAME, PASSWORD, NationalCloud.Global,
TENANT, CLIENT_SECRET)
On using this I get error:
OAuthProblemException{error='invalid_grant', description='AADSTS65001: The user or administrator has not consented to use the application with ID 'e2bfebf6-cc77-49ec-82a3-28756ad377e5' named 'Milpitas Communications'. Send an interactive authorization request for this user and resource.
Trace ID: a2b91757-4849-4680-a089-001831ef7b00
Correlation ID: ae894060-a2ce-444c-9889-96fd3cdfaea7
I also tried to use this to create authprovider:
AuthorizationCodeProvider authProvider = new AuthorizationCodeProvider(CLIENT_ID,
Arrays.asList("https://graph.microsoft.com/user.read", "https://graph.microsoft.com/Mail.ReadWrite",
"https://graph.microsoft.com/Calendars.ReadWrite"),
AUTHORIZATION_CODE, REDIRECT_URL, NationalCloud.Global, "common",
CLIENT_SECRET);
To run the above I need authorization code, can anyone please suggest How I can get the code internally in spring boot application, as my application cannot have client input(for auth) on this?
Or alternatively Is there some other way I can use the IGraphServiceClient for creating a calendar event?
Replace Arrays.asList("https://graph.microsoft.com/user.read", "https://graph.microsoft.com/Mail.ReadWrite", "https://graph.microsoft.com/Calendars.ReadWrite") with Arrays.asList("https://graph.microsoft.com/.default") can resolve this issue.
My code for your reference:
public static void main(String[] args) {
String USERNAME = "{username}";
String PASSWORD = "{password}";
String TENANT = "{tenantID}";
String CLIENT_ID = "{clientID}";
String CLIENT_SECRET = "{clientSecret}";
UsernamePasswordProvider authProvider = new UsernamePasswordProvider(CLIENT_ID,
Arrays.asList("https://graph.microsoft.com/.default"),
USERNAME, PASSWORD, NationalCloud.Global,
TENANT, CLIENT_SECRET);
IGraphServiceClient graphClient = GraphServiceClient
.builder()
.authenticationProvider(authProvider)
.buildClient();
User user = graphClient.me().buildRequest().get();
System.out.println(user.userPrincipalName);
}
Related
My Authorization Client: Angular, Resource Server: Java Spring Boot, Authorization Server: Azure Active Directory
I am using oAuth2 to login via Angular via the PKCE Authorization Flow and then pass the token to the back end.
I am able to access the token in my back end via the Authorization Beaer Header, but when I go to use that token to access Microsoft Graph API, I am getting an Invalid token exception.
com.microsoft.graph.http.GraphServiceException: Error code: InvalidAuthenticationToken
Error message: CompactToken parsing failed with error code: 80049217
I am not sure why it is causing this error, because its valid and I can verify via https://jwt.io/
and access my other protected api in postman with the token.
AuthProvider.java
public class AuthProvider implements IAuthenticationProvider {
private String accessToken = null;
public AuthProvider(String accessToken) {
this.accessToken = accessToken;
}
#Override
public void authenticateRequest(IHttpRequest request) {
// Add the access token in the Authorization header
request.addHeader("Authorization", "Bearer " + accessToken);
}
}
SecurityConfiguration.java
http.cors().and()
.authorizeRequests().antMatchers("/home").permitAll()
.and()
.authorizeRequests().antMatchers("/actuator/health").permitAll()
.and()
.authorizeRequests().antMatchers("/**").authenticated()
.and()
.oauth2ResourceServer().jwt();
GraphAPIController.java
private static IGraphServiceClient graphClient = null;
private static AuthProvider authProvider = null;
private static void ensureGraphClient(String accessToken) {
if (graphClient == null) {
// Create the auth provider
authProvider = new AuthProvider(accessToken);
// Create default logger to only log errors
DefaultLogger logger = new DefaultLogger();
logger.setLoggingLevel(LoggerLevel.ERROR);
// Build a Graph client
graphClient = GraphServiceClient
.builder()
.authenticationProvider(authProvider)
.logger(logger)
.buildClient();
}
}
#GetMapping("/getUser")
public static User getUser(#RequestHeader(value="Authorization") String token) {
System.out.println("THE TOKEN: " +token);
ensureGraphClient(token);
// GET /me to get authenticated user
User me = graphClient
.me()
.buildRequest()
.get();
System.out.println("THE USER: " + me);
return me;
}
My Angular Setup:
app.module:
import { OAuthModule } from 'angular-oauth2-oidc';
app.component.ts
Postman:
An access token can only be for one resource. I can see that you configure scope: 'openid api://{appid}/app' in your Angular Setup. It means the access token is for this resource api://{appid}/app rather than Microsoft Graph https://graph.microsoft.com. That is why you got the InvalidAuthenticationToken Error.
So if you want to call Microsoft Graph in your backend API, you need to consider OAuth 2.0 On-Behalf-Of flow. The OAuth 2.0 On-Behalf-Of flow (OBO) serves the use case where an application invokes a service/web API, which in turn needs to call another service/web API.
In your case, your backend API is web API A and Microsoft Graph is web API B.
A sample for your reference.
I've adapted the Java console application letting users sign-in with username/password to call Microsoft Graph API on behalf of them, but instead of retrieving basic user data to send emails.
However, while the original example works fine (I am getting user email, see commented code below), I am getting this error when sending emails:
Graph service exception Error code: NoPermissionsInAccessToken
Error message: The token contains no permissions, or permissions can not be understood.
For this operation Mail.Send scope needs to be defined in Azure portal and it should be sufficient to use without Admin consent.
I use these Microsoft dependencies:
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph</artifactId>
<version>1.6.0</version>
</dependency>
This is the actual code:
public class MicrosoftGraphMailer {
private final static String CLIENT_APP_ID = "{real-client-app-id}";
private final static String AUTHORITY = "https://login.microsoftonline.com/{real-tenant-id}/";
public static void main(String[] args) throws Exception {
String user = "{real-user}";
String password = "{real-password}";
String token = getUserPasswordAccessToken(user, password).accessToken();
System.out.println(token);
SimpleAuthProvider authProvider = new SimpleAuthProvider(token);
IGraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
Message message = new Message();
message.subject = "My subject";
ItemBody body = new ItemBody();
body.contentType = BodyType.HTML;
body.content = "<h1>My HTML body</h1>";
message.body = body;
List<Recipient> toRecipientList = new LinkedList<>();
Recipient toRecipient = new Recipient();
EmailAddress emailAddress = new EmailAddress();
emailAddress.address = "{real-recipient}";
toRecipient.emailAddress = emailAddress;
toRecipientList.add(toRecipient);
message.toRecipients = toRecipientList;
graphClient.me()
.sendMail(message, false)
.buildRequest()
.post();
/*
String mail = graphClient.me()
.buildRequest()
.get().mail;
System.out.println(mail);
*/
}
private static IAuthenticationResult getUserPasswordAccessToken(String user, String password) throws Exception {
PublicClientApplication app = PublicClientApplication.builder(CLIENT_APP_ID).authority(AUTHORITY).build();
Set<String> scopes = new HashSet<>(Arrays.asList("Mail.Send"));
UserNamePasswordParameters userNamePasswordParam = UserNamePasswordParameters.builder(
scopes, user, password.toCharArray())
.build();
return app.acquireToken(userNamePasswordParam).get();
}
private static class SimpleAuthProvider implements IAuthenticationProvider {
private String accessToken = null;
public SimpleAuthProvider(String accessToken) {
this.accessToken = accessToken;
}
#Override
public void authenticateRequest(IHttpRequest request) {
request.addHeader("Authorization", "Bearer " + accessToken);
}
}
}
Basically I need console daemon app for sending emails on behalf of me without any user interaction. Credentials will be stored outside the app. I don't need permissions to send emails on behalf of arbitrary user.
usually that error message only occurs if its not sending a token to graph or a valid token to graph. To debug, I would first get the token and decode it by either pasting it in jwt.ms or something to see if the required scopes are in the token as expected.
I also wonder if not requesting the user.read scope causes issues for mail.send
Also, you said admin consent need not be given, but in your case it does. because if the admin doesn't consent, then the user must consent. but since you are doing this headless, there is no opportunity for the user to consent. meaning no one consented for this application to send mail as you.
I'm developing an OAuth2.0 "CLIENT" application which call some APIs(secured by oauth2.0).
I'm using OAuth2.0RestTemplate which contains CLIENT_ID, CLIENT_SECRET, username and password. The code for calling OAuth2.0 secured APIs looks like this:
#Bean
OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
List<String> Scopes = new ArrayList<String>(2);
Scopes.add("read");
Scopes.add("write");
resource.setClientAuthenticationScheme(AuthenticationScheme.header);
resource.setId("*****");
resource.setAccessTokenUri(tokenUrl);
resource.setClientId("*****");
resource.setClientSecret("*****");
resource.setGrantType("password");
resource.setScope(Scopes);
resource.setUsername("*****");
resource.setPassword("*****");
return resource;
}
#Autowired
private OAuth2RestTemplate restTemplate;
Map<String, String> allCredentials = new HashMap<>();
allCredentials.put("username", "***");
allCredentials.put("password", "***");
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().setAll(allCredentials);
ParameterizedTypeReference<List<MyObject>> responseType = new ParameterizedTypeReference<List<MyObject>>() { };
ResponseEntity<List<MyObject>> response = restTemplate.exchange("https://***.*****.com/api/*****/*****",
HttpMethod.GET,
null,
responseType);
AllCities all = new AllCities();
all.setAllCities(response.getBody());
As you can see everytime I want to call a service the code get a new ACCESS TOKEN which is wildly wrong!!! My question is how can I automatically receive and store the issued token in my application an use it until it expires and then automatically get a new one?
On the other hand my token only contains access token and doesn't contain refresh token(I don't know why!!! this is so weird!!!)
Hello you can design like google client library.
First step you need to create the datastore for store the token in your directory like C:/User/soyphea/.token/datastore.
Before you load your function retrieve access_token_store. Your access token should have expired_in.
if(access_token_store from your datastore !=null && !expired){
access_token = access_token_store.
} else {
access_token = Your RestTemplate function for retrieve access_token.
}
finally you can retrieve access_token.
In spring security oauth2 if you want to support refresh_token you need to set,
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("resource-serv")
.scopes("read")
.resourceIds("my-resource")
.secret("secret123")
.and()
.withClient("app")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.scopes("read")
.resourceIds("my-resource")
.secret("appclientsecret");
}
First of all you have define that your app is a Oaut2App for this in Spring boot you can use the annotation #EnableOAuth2Client in your code and configure the client application metadata in your applicaition.yml. A skeleton client app can be like below:
#EnableOAuth2Client
#SpringBootApplication
public class HelloOauthServiceApplication {
public static void main(String[] args) {
SpringApplication.run(HelloOauthServiceApplication.class, args);
}
#Bean
public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ProtectedResourceDetails resource){
return new OAuth2RestTemplate(resource);
}
}
application.yml
security:
oauth2:
client:
clientId: client
clientSecret: secret
accessTokenUri: http://localhost:9090/oauth/token
userAuthorizationUri: http://localhost:9090/oauth/authorize
auto-approve-scopes: '.*'
registered-redirect-uri: http://localhost:9090/login
clientAuthenticationScheme: form
grant-type: passwordR
resource:
token-info-uri: http://localhost:9090/oauth/check_token
in this way you have guarantee that the OAuth2RestTemplate of spring will use and upgrade the token
I'm using Spring Boot to login to an external program using its basic authentication. That authentication exists of giving username + password and use Base64 to encode the header. After this I can use a call + header (containing password and username) to retrieve data.
Is there a simple way in Spring Boot to temporary save that header? And after the user is done, he/she can simply remove that header?
Otherwise the user has to keep giving username+password for every call to the API.
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
String url = "url";
RestTemplate template = new RestTemplate();
HttpHeaders headers = createHeaders("mail", "password");
ResponseEntity<JsonSearchResponse> response = template.exchange(url, HttpMethod.GET,
new HttpEntity<JsonSearchResponse>(headers), JsonSearchResponse.class);
JsonSearchResponse obj = response.getBody();
SpringApplication.run(DemoApplication.class, args);
}
public static HttpHeaders createHeaders(String username, String password) {
return new HttpHeaders() {{
String auth = username + ":" + password;
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + new String(encodedAuth);
set("Authorization", authHeader);
}};
}
}
In the end I'm going to put this code in a different class. But just prototyping at the moment.
The application is going to be a web application. Using a database. I'm also going to use Thymeleaf.
All users use the same backend. As it is usually the case with web applications.
I'm trying to write a simple smoke test for a web application.
The application normally uses form based authentication, but accepts basic auth as well, but since the default is form based authentication, it never sends an authentication required, but instead just sends the login form.
In the test I try to send the basic auth header using
WebClient webClient = new WebClient();
DefaultCredentialsProvider creds = new DefaultCredentialsProvider();
// Set some example credentials
creds.addCredentials("usr", "pwd");
// And now add the provider to the webClient instance
webClient.setCredentialsProvider(creds);
webClient.getPage("<some url>")
I also tried stuffing the credentials in a WebRequest object and passing that to the webClient.getPage method.
But on the server I don't get an authentication header. I suspect the WebClient only sends the authentication header if it get explicitly asked for it by the server, which never happens.
So the question is how can I make the WebClient send the Authentication header on every request, including the first one?
This might help:
WebClient.addRequestHeader(String name, String value)
More specific one can create an authentication header like this
private static void setCredentials(WebClient webClient)
{
String username = "user";
String password = "password";
String base64encodedUsernameAndPassword = base64Encode(username + ":" + password);
webClient.addRequestHeader("Authorization", "Basic " + base64encodedUsernameAndPassword);
}
private static String base64Encode(String stringToEncode)
{
return DatatypeConverter.printBase64Binary(stringToEncode.getBytes());
}