Microsoft Graph suddenty returning "invalid_grant" for new users, still works fine locally - microsoft-graph-api

I've been using Microsoft Graph for months to get Calendar's read and write access for my app. Everything's was working fine until a few days ago where new user would get the following error message when following the authentification process for my Microsoft Graph app :
{"error":"invalid_grant","error_description":"Bad Request"}
The most surprising thing is that if I try the process locally (so using a local URL in "redirectUri"), it's working fine.
It's working fine on my local machine, there's no difference between the code on my local machine and the one in production except for the "RedirectURI" variable that is different.
Here's the code snippet for the authorisation url :
const OAUTH_APP_ID = "XXXXXXXXXXXXXX";
const OAUTH_APP_SECRET = "XXXXXXXXXXXXX";
const OAUTH_REDIRECT_URI = "XXXXXXXXXXXX";
const OAUTH_SCOPES= "openid profile offline_access user.read mailboxsettings.read calendars.readwrite";
const OAUTH_AUTHORITY= "https://login.microsoftonline.com/common";
const OAUTH_AUTHORIZE_ENDPOINT= "/oauth2/v2.0/authorize";
const OAUTH_TOKEN_ENDPOINT = "/oauth2/v2.0/token";
// Initialize the OAuth client
$oauthClient = new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => MicrosoftGraphManager::OAUTH_APP_ID,
'clientSecret' => MicrosoftGraphManager::OAUTH_APP_SECRET,
'redirectUri' => MicrosoftGraphManager::OAUTH_REDIRECT_URI,
'urlAuthorize' => MicrosoftGraphManager::OAUTH_AUTHORITY.MicrosoftGraphManager::OAUTH_AUTHORIZE_ENDPOINT,
'urlAccessToken' => MicrosoftGraphManager::OAUTH_AUTHORITY.MicrosoftGraphManager::OAUTH_TOKEN_ENDPOINT,
'urlResourceOwnerDetails' => '',
'scopes' => MicrosoftGraphManager::OAUTH_SCOPES
]);
$authUrl = $oauthClient->getAuthorizationUrl();
// Save client state so we can validate in callback
$Session = $Request->getSession();
$Session->set("oauthState", $oauthClient->getState());
$extraParam = "&prompt=select_account";
$finalUrl = $authUrl.$extraParam;
If I set the "OAUTH_REDIRECT_URI" variable to my local environnment and try an authentification there, it's working fine, but if I change this url to the one in production and try the authentification on my production server, it's not working anymore.
Thanks!

This depends on which authorization flow you are using, if you want to access the other user calendar events or mails you need to use Client Credentials flow.

Related

Can't get OAuth 2 Scope. It is not set. Quickbook

I have successfully integrated Quickbook accounting APIs with the Development Keys and Credentials. Moreover, when I put Production Keys and Secret, I got an error with status code (403), and later I got to know that is because of scopes.
I noticed there is a function called "getScope()" to check the token scopes. I went to try that and get the following error:
Can't get OAuth 2 Scope. It is not set.
I even tried using access and refresh tokens from the API playground. Still, the error is there.
Here is my code snippet:
$dataService = DataService::Configure(array(
'auth_mode' => 'oauth2',
'ClientID' => $quickBookData->client_id,
'ClientSecret' => $quickBookData->client_secret,
'accessTokenKey' => $quickBookData->access_token,
'refreshTokenKey' => $quickBookData->refresh_token,
'QBORealmID' => $quickBookData->realm_id,
'baseUrl' => "production",
));
$OAuth2LoginHelper = $dataService->getOAuth2LoginHelper();
$scopes = $OAuth2LoginHelper->getScope();

Created a user pool client using Cognito Identity Provider Client SDK for JavaScript v3, but can't fetch token using (client_credentials) grant type

Created a user pool client using Cognito Identity Provider Client SDK for JavaScript v3
npm install #aws-sdk/client-cognito-identity-provider.
The following code shows how I created the resources server and the user pool client, using the mentioned👆 SDK...
let poolName = 'UserPool';
const client =new CognitoIdentityProviderClient({
region: process.env.COGNITO_AWS_REGION
});
// create resource server
const createResourceServerCommand = new CreateResourceServerCommand({
Name: poolName,
UserPoolId: UserPool.Id,
Identifier: 'https://localhost:8080/api/v2',
Scopes: [
{
ScopeName: 'access',
ScopeDescription: 'General access to API'
}
]
});
const { ResourceServer } = await client.send(createResourceServerCommand);
// create the user pool client
const createUserPoolClientCommand = new CreateUserPoolClientCommand({
ClientName: 'Default',
UserPoolId: UserPool.Id,
ExplicitAuthFlows: ['USER_PASSWORD_AUTH'],
GenerateSecret: true,
AllowedOAuthFlows: ['client_credentials'],
SupportedIdentityProviders: ['COGNITO'],
AllowedOAuthScopes: [ 'https://localhost:8080/api/v2/access' ]
});
const { UserPoolClient } = await client.send(createUserPoolClientCommand);
...but, I can't fetch tokens using the grant type client_credentials. Therefore getting the following error.
{
"error": "invalid_grant"
}
However, if I use AWS console to navigate to the user pool > Client > Edit the hosted UI and click on the save button without making any changes...
... I am able to fetch a token using the client_credentials grant type.
Is there any setting that I might be missing in the above code that AWS console is setting? I need the following code to automate the creation of user pools.
When I switched to the old I noticed this notification
Apparently, Oauth flows are not enabled by default. Hence adding the following attribute to the CreateUserPoolClientCommandInput object AllowedOAuthFlowsUserPoolClient: true enables it. Hope this helps some newbie like me out there.

Discord Oauth2 receiving 'invalid client' error

I had Discord Oauth2 implemented so that my users could log into my website by authenticating through Discord. For months, everything worked great and now all of the sudden it stopped working.
Per Discord's oauth2 instructions,https://discordapp.com/developers/docs/topics/oauth2#shared-resources, I am able to successfully acquire the access code that is meant to be traded for the access token. However, when I try to receive the access token I receive an 'invalid_client' error.
First, I am hitting this endpoint:
https://discordapp.com/api/oauth2/authorize?client_id=${process.env.CLIENT_ID}&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Flogin%2Fdiscord%2Fcallback&response_type=code&scope=identify%20email%20gdm.join
which successfully returns the following:
http://localhost:5000/login/discord/callback?code={some_access_code}
The access code is then sent back to discord to obtain the access token. Here is the code that is failing:
export function getDiscordAccessToken(accessCode, call) {
const redirect = call === 'login' ? process.env.DISCORD_LOGIN_REDIRECT : process.env.DISCORD_CONNECT_REDIRECT
return new Promise((resolve, reject) => {
axios
.post(
`https://discordapp.com/api/oauth2/token?client_id=${process.env.DISCORD_CLIENTID}&client_secret=${process.env.DISCORD_SECRET}&grant_type=authorization_code&code=${accessCode}&redirect_uri=${redirect}&scope=identify%20email%20gdm.join`
)
.then(res => {
resolve(res.data)
})
.catch(err => {
// log error to db
console.log("Here is your error: ", err.response)
reject(err.response)
})
})
}
This code was working for months with no problems. Then, all of the sudden it stopped working. I even checked the Discord change logs which can be found here, https://discordapp.com/developers/docs/change-log, but I found no reference to authentication changes.
Any help you can provide is greatly appreciated!
The query parameters should be in the BODY of the POST request, not the URL for the oauth/token url.
Discord recently pushed a update to the oAuth2 which makes it confine more with the standard. This means they no longer support parameters in the URL for POST, but instead require them to be in the body and form encoded (basically the same, but in the body and without the leading ?).
So you basically need (not tested):
axios.post(
`https://discordapp.com/api/oauth2/token`,
`client_id=${process.env.DISCORD_CLIENTID}&client_secret=${process.env.DISCORD_SECRET}&grant_type=client_credentials&code=${accessCode}&redirect_uri=${redirect}&scope=identify%20email%20gdm.join`
)
I know the question has already been answered, but in my case I copied a wrong secret key. Just make sure that you copy the right one.
Secret Key is located under OAuth2 Tab and not under General Information tab on discord developer's dashboard.

Is it possible to use OAuth 2.0 for Office365 SMTP?

I have an email application for sending emails that was written in house. We have set it with the option to use OAuth 2.0 with GMail (personal and business accounts) and Outlook.com accounts without issues.
We can also authentication with user ids and passwords but we prefer OAuth 2.0 as we don't save passwords anywhere that way.
We now have requests to do this for Office365 accounts.
I notice that the hello message on the Office365 smtp server (smtp.office365.com port 587) does not offer the XOAUTH2 option.
250-BY2PR0601CA0005.outlook.office365.com Hello [xx.xx.xx.xx]
250-SIZE 157286400
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-AUTH LOGIN
250-8BITMIME
250-BINARYMIME
250 CHUNKING
But, the SMTP server for outlook.com does:
250-BLU436-SMTP14.smtp.hotmail.com Hello [xx.xx.xx.xx]
250-TURN
250-SIZE 41943040
250-ETRN
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-8bitmime
250-BINARYMIME
250-CHUNKING
250-VRFY
250-AUTH LOGIN PLAIN XOAUTH2
250 OK
Is this possible to do with Office365? If not, can we point Office365 users to the outlook.com smtp server (smtp-mail.outlook.com) or are they totally different?
We'd rather not use the APIs just for sending emails if possible as the RESTful APIs for each provider will of course be quite different.
The reason for using OAuth 2.0 when sending email with an Office365 account is that we don't want to have to store passwords on our server. Also, if the user changes their password, we won't know unless they tell us or manually update it on our system side.
Using OAuth 2.0 this would solve this problem and allow the application to flow like with other email providers.
I really wanted this feature too. It would make Office365 apps that need to send mail that much easier!
I did some hunting, and found this which appears to be as close to an official answer as we are going to get (and the answer is a flat no).
Not sure if I'm missing something, but isn't this what you want? Looks like this was posted back in February. Interestingly, this article says that Oauth is supported for M365, but NOT for outlook.com users.
https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
I made one example using javax.Mail and OAuth for desktop application. It opens logon screen to get acccessToken. I followed multiple instructions so probably there are too many permissions and props in JavaMail but I succeeded to send mail.
My example program (Github)
PHP Example with OAuth2.
[On GitHub] (https://github.com/larsonnn/php_smtp_xoauth2_microsoft.php)
<?php
/* composer.json
"require": {
"phpmailer/phpmailer": "^6.6",
"league/oauth2-client": "^2.6",
"thenetworg/oauth2-azure": "^2.1"
}
*/
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\PHPMailer;
use TheNetworg\OAuth2\Client\Provider\Azure;
require "vendor/autoload.php";
$mail = new PHPMailer(true);
$provider = new Azure([
'clientId' => '',
'clientSecret' => '',
"scopes" => ["https://outlook.office.com/SMTP.Send"],
"tenant" => "",
"defaultEndPointVersion" => Azure::ENDPOINT_VERSION_2_0,
]);
$mail->setOAuth(
new OAuth(
[
'provider' => $provider,
'clientId' => '',
'clientSecret' => '',
'refreshToken' => '',
'userName' => 'mymail#office_365_email.tld',
]
)
);
//Server settings
$mail->SMTPDebug = SMTP::DEBUG_SERVER;
$mail->isSMTP();
$mail->Host = 'smtp.office365.com';
$mail->Port = 587;
$mail->SMTPAuth = true;
$mail->AuthType = 'XOAUTH2';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->CharSet = PHPMailer::CHARSET_UTF8;
//Recipients
$mail->setFrom('mymail#office_365_email.tld', 'name');
$mail->addAddress('spam#example.tld', 'Spam');
//Content
$mail->Subject = 'Here is the subject';
$mail->Body = 'Hallo';
$mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
$mail->send();

Cannot get oAuth2 Access Token for Google Analytics API

I am using Rails + Garb Gem (Sija Branch) + omniauth-google-oauth2 Gem and I can successfully authenticate with the Google Analytics API and extract data that our app is generating when using a user login, e.g.:
Garb::Session.login('USERNAME', '<PASSWORD>')
I can then use Garb to connect to the Analytics Profile I want and pull the data from it and display some charts on a webpage. This all works fine.
However, I want to use oAuth2 to authenticate with Analytics which is why I had to install the Sija branch of the Garb Gem from Github (it supports oAuth2) and I also installed the omniauth-google-oauth2 Gem. Now in theory I should be able to authenticate using the following code:
Garb::Session.access_token = access_token # an instance of OAuth2::Client
It's at this point that it gets a little hazy for me and I would greatly appreciate some guidance. Here's how far I have gotten:
1) I created a Project in the Google API console and turned on Analytics API under Services
2) This provided me with a Client ID and Client Secret
3) I came across this code which I could populate with the ID and Secret above:
client = OAuth2::Client.new(
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
{
:site => 'https://accounts.google.com',
:authorize_url => '/o/oauth2/auth',
:token_url => '/o/oauth2/token'
})
4) Then there is the next bit of code:
response = OAuth2::AccessToken.new(
client,
STORED_TOKEN, {
refresh_token: STORED_REFRESH_TOKEN,
expires_at: STORED_EXPIRES_AT
})
5) and then in theory connect with:
Garb::Session.access_token = response
The problem I have is I don't have the token information in Point (4) above. It seems to me that with oAuth2 I need to do a "handshake" once and print out the return token values? Perhaps through Rails code which prints the values returned out and then paste the token values into a constant in the Rails app so that I can use them in the above code? I really am confused. As I mentioned earlier, the web app works fine using the user login authentication. All the web app is doing is authenticating with analytics, pulling down some data and drawing a chart. But I am stuck converting it over to oAuth2 as I just do not know how to get the Access Token that the Garb Gem is looking for. I should also note that this is not a public website with multiple users authenticating, this is a CMS website that is connecting to our own Analytics data.
I have seen some partial snippets of aspects of this but not a fully explained or working example. I would really appreciate any guidance and help with this question.
Many thanks in advance,
JR
I've soldiered through this over the last few weeks, so let me share what worked:
To use Oauth2 you need to get a 'refresh token' that you use to 're-authenticate' with google each time you make an API call. The steps for this are as follows:
1) Setup your account in the API console - https://code.google.com/apis/console/b/0/ (seems like you've done that well)
2) In your API account, make sure you have a redirect URI pointing back to your application:
http://some-url.com/auth/google_oauth2/callback
http://localhost:3000/auth/google_oauth2/callback
Note here that google won't let you call back to your local machine as 0.0.0.0:3000... so you'll need to use localhost explicitly
3) In your route file, tie that redirect url to an action in the controller where you're going to create the project or authentication
match '/auth/:provider/callback' => 'authentications#create'
The ':provider' simply lets you match on multiple types of oauth, but you could just put 'google_oauth2' there as well.
4) Now create that action in your controller
def create
auth = request.env["omniauth.auth"]
params = request.env["omniauth.params"]
project = Project.find(params['project_id'])
Authentication.create(:project_id => project.id, :provider => auth['provider'], :uid => auth['uid'], :access_token => auth['credentials']['refresh_token'])
flash[:notice] = "Authentication successful."
redirect_to owner_view_project_path(project)
end
5) The controller action should retrieve the relevant fields from the response object (details of response object here: https://github.com/zquestz/omniauth-google-oauth2) - in particular, you need to get the 'refresh_token' and save that to your project or authentication object - if you haven't added an 'access_token' attribute to the desired object, go do that now with a migration, then start saving the refresh token to that attribute
6) Now when you're ready to call that particular authentication and get API data for it, you can load up that object where you saved the access token, and use that to get a new session with the google API as follows:
#authentication = Authentications.find(params[:id])
client = OAuth2::Client.new GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
{
:site => 'https://accounts.google.com',
:authorize_url => "/o/oauth2/auth",
:token_url => "/o/oauth2/token",
}
response = OAuth2::AccessToken.from_hash(client, :refresh_token => #authentication.access_token).refresh!
Garb::Session.access_token = response
#profiles = Garb::Management::Profile.all
What this code did was create an OAuth2 access token (response) by specifying the client and then a refresh_token, then calling 'refresh!' to get a refreshed access token... then use that access token to establish your Garb session, then call down all the profiles for a given account using the Gard::Management::Profile.all
Hope this helps - let me know if you have questions!
Just a note on what worked for me in:
For steps 3, 4 & 5 I used cURL instead to retrieve the Access/Refresh token. Step 6 is then the same for me (using the Sija branch of the Garb Gem). So using cURL:
Using the details associated with your Google app POST the following using cURL:
curl --data "code=<APP_CODE>&redirect_uri=http://localhost:3000/oauth2callback&client_id=<CLIENT_ID>.apps.googleusercontent.com&scope=&client_secret=<CLIENT_SECRET>&grant_type=authorization_code" https://accounts.google.com/o/oauth2/token
The response takes the form:
{
"access_token" : "<ACCESS_TOKEN>",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "<REFRESH_TOKEN>"
}
which you can plug into the Garb Gem as per part 6.
The answer by #CamNorgate is valid.
If you don't have a "refresh_token" back from Omniauth on the callback make sure you are correctly initializing :google_oauth2
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"],
{ :scope=>"https://www.google.com/m8/feeds, https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile",
:approval_prompt=>"force", access_type="offline"
}
end
Make sure to include :approval_prompt=>"force", access_type="offline" in order for the refresh_token to be sent back. The refresh_token is only provided on the first authorization from the user.

Resources