I try to connect from my App (Spring Boot application) to Gmail API in order to download some of my mail. It actually works, but I have a problem with authorization. When I call authorize() method, it gives me a auth link in IDE console. Is there any way to redirect user to authorization page instead of print this link in console?
My code:
public Credential authorize() throws IOException
{
InputStream inputStream = GTD.class.getResourceAsStream("/client_secret.json");
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(inputStream));
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(DATA_STORE_FACTORY)
.setAccessType("offline")
.build();
Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
System.out.println("Credentials saved to " + DATA_STORE_DIR.getAbsolutePath());
return credential;
}
public Gmail getGmailService() throws IOException
{
Credential credential = authorize();
return new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("GTD")
.build();
}
EDIT: When I call functions from normal Java App, not web application, it works.
Related
My application has an API server (Spring Boot) and a client-facing server (React), the React server will make API requests on behalf of an authenticated user. For authentication, I use a CAS server provided by a third-party.
I used the Java Apereo CAS Client (version 3.6.2), and add the following API endpoints for the React app to call:
#RestController
public class CASController {
#Value("${cas.server-url-prefix}")
private String CASUrl;
#GetMapping("/login")
public String getCASUserId(HttpServletRequest request){
return request.getRemoteUser();
}
#GetMapping("/logout")
public void logout(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession(false);
if(session != null || !request.isRequestedSessionIdValid()) {
String id = session.getId();
session.invalidate();
log.info("JSESSIONID: " + id + " is valid: " + request.isRequestedSessionIdValid());
}
try{
response.sendRedirect(CASUrl + "/logout");
}
catch (IOException | IllegalStateException e){
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
}
}
}
The above endpoints work if the Spring Boot app interacts with the client directly but it doesn't, it accepts requests from the React server. When the React app tries to call: localhost:8080/login, it failed because of the redirection.
A way to work around this is to call the CAS server from the React app, then send the required info (service URL & ticket issued by the CAS server) to the backend, and let the backend make the ticket validation call, if the ticket validation success, then the user is authenticated and a session id is sent back to the React app for subsequent requests.
My questions are:
Does the workaround even make sense? Authentification is split between the frontend (get a ticket from CAS server) and the backend (validate the issued ticket with CAS server and issue session). If it doesn't, what is a better solution?
For the workaround, I think I will need to use Spring Security for this instead of Java Apereo CAS Client? If that's the case, how do I do it?
I tried something like this:
Ticket validation endpoint:
#GetMapping("/login")
public String getCASUserId(#RequestParam #NotBlank String URL,
#RequestParam #NotBlank String ticket,
HttpServletRequest request){
// make the call the the CAS server for ticket validation
String userId = userService.getUserId(url, ticket);
HttpSession session = request.getSession();
session.setAttribute("userId", userId);
return userId;
}
The ticket is validated, session issued but no endpoint is actually protected since neither spring-security nor Java Apereo CAS Client is enabled.
I am using Graph API to send emails and using clientID, Secret and APPID. I'm getting the token, but I'm unable to send emails:
Code: NoPermissionsInAccessToken Message: The token contains no
permissions, or permissions can not be understood. Inner error:
AdditionalData: requestId: 53f1cddb-4f38-4efa-ab62-624b495374f0 date:
2020-12-02T10:46:42 ClientRequestId:
53f1cddb-4f38-4efa-ab62-624b495374f0
I have added API permission as delegated to mail.send
IPublicClientApplication publicclientapplication = PublicClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.Build();
UsernamePasswordProvider authprovider = new UsernamePasswordProvider(publicclientapplication,scopes);
var authResult = await publicclientapplication
.AcquireTokenByUsernamePassword(scopes, username, passwordstring(stringpassword))
.ExecuteAsync().ConfigureAwait(false);
return authResult.AccessToken;
Code that I'm using to send is:
await graphServiceClient.Me .SendMail(email, false) .Request() .PostAsync();
Please note that delegated permission is for app+user authentication while application permission is for app-only authentication.
You should have used a wrong Microsoft Graph authentication provider or you have added a wrong type of permission.
Now you should be using Client credentials provider, which uses client credential flow and application permission. But you didn't add application permission in your Azure AD app. It's why you get the error The token contains no permissions. If your application doesn't require a user to sign in, you could use this client credentials provider. But you need to add application permission instead of delegated permission into AAD app and remember to use graphServiceClient.Users["{id or userPrincipalName}"].SendMail to send the mail.
For delegated permission and graphServiceClient.Me endpoint, you should choose Authorization code provider which requires you to implement sign-in interactively. Keep using delegated permission and graphServiceClient.Me.SendMail.
If you don't want to sign in interactively but want to have a user in the access token, you need to choose Username/password provider. But it's not recommended by Microsoft. See Warning here. Keep using delegated permission and graphServiceClient.Me.SendMail as well.
UPDATE:
A sample with Username/password provider:
IPublicClientApplication publicClientApplication = PublicClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.Build();
UsernamePasswordProvider authProvider = new UsernamePasswordProvider(publicClientApplication, scopes);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var email = "Your Username Here";
var str = "Your Password Here";
var password = new SecureString();
foreach (char c in str) password.AppendChar(c);
//prepare message and saveToSentItems here
await graphClient.Me
.SendMail(message,saveToSentItems)
.Request()
.PostAsync();
UPDATE 2:
Add application permission Mail.Send into Azure AD app.
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
//prepare message and saveToSentItems here
await graphClient.Users["{id or userPrincipalName}"]
.SendMail(message,saveToSentItems)
.Request()
.PostAsync();
After successful AD authentication from my MVC web app, I set the token in the header so that client side scripts can make use of it to access the web api.
OnAuthorizationCodeReceived() after a successful login
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
Uri uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, graphResourceId);
HttpContext.Current.Request.Headers.Add("Authorization", "Bearer " + result.AccessToken);
}
In the last line I set the "Authorization" header. However I can't seem to make use of that from my AngularJS end to make api calls! What am I missing here? Requirement is that I don't have to use ADAL to authenticate from the AngularJS since web app already authenticates the user and have passed me the valid tokens.
You could try to save the access token in cookie :
HttpCookie tokenCookies = new HttpCookie("token");
tokenCookies.Value = result.AccessToken;
tokenCookies.Expires = DateTime.Now.AddHours(1);
HttpContext.Current.Response.Cookies.Add(tokenCookies);
Then making ajax call with access token from cookie on client side .
In googel app engine, if we are not using OpenID login, we can send mails like its written in API
But I use OpenId Login (using Google mail) and I cant use this.
But I do something like thatL
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.gmail.com");
props.put("mail.smtp.socketFactory.port", "465");
props.put("mail.smtp.socketFactory.class","javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.port", "465");
Session session = Session.getDefaultInstance(props, new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
"mail#gmail.com", "pass");
}
});
**Message message = new MimeMessage(session);
message.setFrom(new InternetAddress("mail"));
message.setRecipients(Message.RecipientType.TO,InternetAddress.parse("mail"));
message.setSubject("Testing Subject");
message.setText(msgBody);
Transport.send(message);
IF I WILL LOG IN AND THEN CALL THAT SERVLET, for instance www.example.appspot.com/mail it works! but if I'm not logged in, it does not work!
But I don't understand what happens?!**
java.lang.RuntimeException: javax.mail.SendFailedException: Send failure (javax.mail.MessagingException: Illegal Arguments (java.lang.IllegalArgumentException: Unauthorized Sender: Unauthorized sender))
at test.queue.MailServlet.sendMail(MailServlet.java:208)
at this 208 line I have this:
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("example#gmail.com","pass");
}
the address set in the "from" should be present under the Admission -> Permissions for the gae application, and for sending email from this permitted account, no need to specify password in the code.
"For security purposes, the sender address of a message must be the email address of an administrator for the application or any valid email receiving address for the app (see Receiving Mail). The sender can also be the Google Account email address of the current user who is signed in, if the user's account is a Gmail account or is on a domain managed by Google Apps." - as mentioned here https://developers.google.com/appengine/docs/java/mail/#Java_Sending_mail_with_the_JavaMail_API
I want to use GAE Open-ID Federated Authentication using Java Script (without Java Servlet) End Point.
Is it supported via Java Script (without Java Servlet) End Point?
If yes how could I use Open-ID Federated Authentication with Java Script end point API call?
I had tried User in endpoint API
#ApiMethod(
name = "signMe.signGoogleId",
httpMethod = "POST",
scopes = { "https://www.googleapis.com/auth/userinfo.profile" , "https://www.googleapis.com/auth/userinfo.email" }
)
public SignIn signInOpenId(User user)throws
OAuthRequestException, IOException
{
User is null always even after logging with Google\Yahoo User.
I had tried HttpServletRequest req in endpoint API.
#ApiMethod(
name = "SignMe.signOpenId",
httpMethod = HttpMethod.GET,
scopes = { "https://www.googleapis.com/auth/userinfo.profile" , "https://www.googleapis.com/auth/userinfo.email" }
)
public SignIn signInOpenId(HttpServletRequest req)
throws IOException
{
UserService userService = UserServiceFactory.getUserService();
User newUser = userService.getCurrentUser();
newUser is null always even after logging with Google\Yahoo User.
Thanks,
Deepak
You could always dump the cloud endpoints way of doing it and go with general API authentication.
https://developers.google.com/appengine/articles/openid#fa
As would be done for non Cloud Endpoints applications. The down side is you wouldn't get any client authorization abilities, so any client code hit your API.
Google Cloud Endpoints OAuth does not seem to support Open ID at this time.