BadRequest when calling ProcessUserAuthorization after requesting "plus.login" scope - oauth-2.0

I have problems while updating code to new scope. Currently I use userinfo.profile and everything works Ok.
According to Google+ OAuth 2.0 scopes new plus.me scope allows application to know who user is, and plus.login also gives additional access (to age, language, circles, ...).
If I replace userinfo.profile with plus.me - everything works Ok: method WebServerClient.RequestUserAuthorization authorizes user and ProcessUserAuthorization gives me a token.
But if I ask plus.login scope instead - Google adds additional query parameters to my callback and next call to WebServerClient.ProcessUserAuthorization fails , because implementation uses current rul to make new redirect_url, striping "known" parameters and leaving "new unknown" Google parameters. his redirect_url doesn't match on registered in Google Api Console and Google server returns 400 response.
Here is success response from authorization with plus.me scope (from NetOpenAuth.Messaging.Channel log):
Incoming HTTP request: GET http://localhost:40004/Me/LoginComplete?from=Google&state=TWXf6Zq3XYSlwyfCDt3GiQ&code=4/qC_KeuiykcVm1sayIyEdnBjiklxz.AoMfk5TqaXQcsNf4jSVKMpY-GxwThAI
After binding element processing, the received EndUserAuthorizationSuccessAuthCodeResponse (2.0) message is:
code: 4/qC_KeuiykcVm1sayIyEdnBjiklxz.AoMfk5TqaXQcsNf4jSVKMpY-GxwThAI
state: TWXf6Zq3XYSlwyfCDt3GiQ
from: Google
And here is (success) authorization response with plus.login scope:
Incoming HTTP request: GET http://localhost:40004/Me/LoginComplete?from=Google&state=26R-O3YN6u3-5EKIlJzFFw&code=4/zOoeVq8vec068x2-CyPq4PjPNtRT.osemzp8Zl7sQsNf4jSVKMpbcmWQThAI&authuser=0&prompt=consent&session_state=27ca4bd2e70d0721bc1fa781b900a558e59fe4c7..d409
After binding element processing, the received EndUserAuthorizationSuccessAuthCodeResponse (2.0) message is:
code: 4/zOoeVq8vec068x2-CyPq4PjPNtRT.osemzp8Zl7sQsNf4jSVKMpbcmWQThAI
state: 26R-O3YN6u3-5EKIlJzFFw
from: Google
authuser: 0
prompt: consent
session_state: 27ca4bd2e70d0721bc1fa781b900a558e59fe4c7..d409
Call for token (for plus.me scope) will be successful:
Prepared outgoing AccessTokenAuthorizationCodeRequestC (2.0) message for https://accounts.google.com/o/oauth2/token:
code: 4/qC_KeuiykcVm1sayIyEdnBjiklxz.AoMfk5TqaXQcsNf4jSVKMpY-GxwThAI
redirect_uri: http://localhost:40004/Me/LoginComplete?from=Google
grant_type: authorization_code
client_id: 175802076419.apps.googleusercontent.com
client_secret: ********
But with plus.login scope 3 new parameters (authuser, prompt, session_state) are transferred to redirect_url param:
Prepared outgoing AccessTokenAuthorizationCodeRequestC (2.0) message for https://accounts.google.com/o/oauth2/token:
code: 4/zOoeVq8vec068x2-CyPq4PjPNtRT.osemzp8Zl7sQsNf4jSVKMpbcmWQThAI
redirect_uri: http://localhost:40004/Me/LoginComplete?from=Google&authuser=0&prompt=consent&session_state=27ca4bd2e70d0721bc1fa781b900a558e59fe4c7..d409
grant_type: authorization_code
client_id: 175802076419.apps.googleusercontent.com
client_secret: ********
And as soon as this redirect_url does not match registered I receive error:
https://accounts.google.com/o/oauth2/token returned 400 BadRequest: Bad Request
OAuth2 spec says that params code and state are required in authorization response and says noting about adding other parameters. But it also says that
The client MUST ignore unrecognized response parameters
Does this mean that this is DNOA issue and not Google one?
May be DNOA must add nullable responseUri parameter to ProcessUserAuthorization and use it instead of guessing from current url...
What's the easiest workaround (except using Google library)?
Update:
Here is original request for /oauth
Prepared outgoing EndUserAuthorizationRequestC (2.0) message for https://accounts.google.com/o/oauth2/auth:
client_id: 175802076419.apps.googleusercontent.com
redirect_uri: http://localhost:40004/Me/LoginComplete?from=Google
state: TWXf6Zq3XYSlwyfCDt3GiQ
scope: https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me
response_type: code

Related

omniauth_openid_connect gem - Authentication failure! invalid_request: Rack::OAuth2::Client::Error, invalid_request :: Client credentials are invalid

Im using this gem to add Omniauth OpenID with a provider.
I configured the gem in the Devise Initializer, everything seems to be correct:
config.omniauth :openid_connect,
{
name: :openid_connect,
scope: %i[openid profile groups_rewardops scope_rewardops],
issuer: ConfigSettings.desjardins.issuer_url,
response_type: :code,
uid_field: 'sub',
response_mode: :query,
discovery: true,
send_scope_to_token_endpoint: false,
client_options:
{
port: 443,
scheme: "https",
host: ConfigSettings.desjardins.host,
authorization_endpoint: "/affwebservices/CASSO/oidc/rewardops/authorize",
token_endpoint: "/affwebservices/CASSO/oidc/rewardops/token",
userinfo_endpoint: "/affwebservices/CASSO/oidc/rewardops/userinfo",
identifier: ConfigSettings.desjardins.client_id,
secret: ConfigSettings.desjardins.client_secret,
redirect_uri: "#{ConfigSettings.api.base_url}front_end/users/auth/openid_connect/callback",
},
}
The flow I have atm is that the user can log in and grant access from the provider, then the provider sends a request to my devise callback url with the nonce, code and state. At this point everything seems to be correct but that request ends in failure when trying to generate the access_token with the following error:
ERROR -- omniauth: (openid_connect) Authentication failure! invalid_request: Rack::OAuth2::Client::Error, invalid_request :: Client credentials are invalid.
Im sure the identifier and the secret are correct, don't understand what's going on.
Since Im using discovery mode all the configs of the provider are in the .well-known you can check it here
Im blocked without ideas about how to debug the error. Checking at Rack::OAuth2 to see where the error is comming from I found this that says:
invalid_request: "The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.",
It seems for some reason the access token request is malformed, but not sure what else apart of identifier and secret should I have in mind? I have seen many other examples of configuration and mine seems to be correct.
Since you are sure your credentials are correct, I suspect there is mismatch between the authentication method being used and the methods supported by the provider. Checking the .well-known config, I see this provider only supports client_secret_post. In your omniauth config, I see no options being passed to specify the authentication method. When I dive down into the code, I see that the underlying oauth2 gem defaults to using basic auth, which uses the indentifier and secret to construct an Authentication header. See: source code here
client_auth_method = args.first || options.delete(:client_auth_method).try(:to_sym) || :basic
case client_auth_method
when :basic
cred = Base64.strict_encode64 [
Util.www_form_url_encode(identifier),
Util.www_form_url_encode(secret)
].join(':')
headers.merge!(
'Authorization' => "Basic #{cred}"
)
In the client_secret_post authentication method, instead of providing client secret in the header, the client authorizes itself providing the secret in the HTTP request body as a form parameter. So this provider is not seeing your credentials. You could verify this by looking at the logs of the token endpoint request, which won't be visible in the browser, but rather from your rails BE to the the provider's server.
Try passing a client_auth_method in the client_options hash in your omniauth config. If you look at the case statement in the code I linked to above, there doesn't seem to be a named option for client_secret_post, but it is the default case. Any value for client_auth_method looks like it would work, but I would still use client_secret_post.

Trouble Establishing Ouath2 Connection to Microsoft Dynamics via Rails Oauth2 gem

I am new to authentication using Oauth2, and was hoping someone could provide some guidance on how to use the oauth2 gem to correctly perform authentication so as to get a token with Microsoft Dynamics.
I have been able to authorize and get a token with Postman, but as these types of applications greatly facilitate the overall process, it can be difficult to map these things to code, especially when the concepts are new.
For the access token, I have at my disposal:
The Auth URL, which is of the form "https://login.windows.net/<CUSTOMER_IDENTIFIER_HASH>/authorize?resource=https://api.businesscentral.dynamics.com
The Access Token URL, which is of the form "https://login.windows.net/<CUSTOMER_IDENTIFIER_HASH>/oauth2/token?resource=https://api.businesscentral.dynamics.com"
The client_id
The client_secret
I've tried the various examples online, but I either get a nondescript error from oauth2 such as:
OAuth2::Error ():
Or, in other cases, something more particular:
OAuth2::Error ({"code"=>"RequestDataInvalid", "message"=>"Request data is invalid."}:
{"error": {"code": "RequestDataInvalid","message": "Request data is invalid."}}):
Does anyone have any real, working examples on how to successfully obtain a token?
Finaly cracked it.
Had to respecitvely move the resource element out of the auth and the access token urls to:
https://login.windows.net/<CUSTOMER_IDENTIFIER_HASH>/authorize
https://login.windows.net/<CUSTOMER_IDENTIFIER_HASH>/oauth2/token
At that point, i set the client as:
client = OAuth2::Client.new(
client_id,
client_secret,
site: base_url,
grant_type: "client_credentials",
resource: "https://api.businesscentral.dynamics.com",
authorize_url: auth_url,
token_url: token_url,
)
Above, the base_url is:
https://api.businesscentral.dynamics.com/v2.0/<CUSTOMER_IDENTIFIER_HASH>
Then, i call the client to get an auth_code and had to explicitly pass the resource parameter:
client.auth_code.authorize_url(:redirect_uri => 'http://localhost:8080/oauth2/callback', resource: "https://api.businesscentral.dynamics.com")
I'm not sure if the resource is necessary when getting the token, but finally, I obtain it as follows:
token = client.password.get_token(<AUTHENTICATION_LOGIN>, <AUTHENTICATION_PASS>, resource: "https://api.businesscentral.dynamics.com")
then i can use the token to perform get commands:
token.get('https://api.businesscentral.dynamics.com/v2.0/Sandbox/api/v2.0/companies(<COMPANY_ID_HASH>)/customers', :headers => { 'Accept' => 'application/json' })
I have a feeling I have to do some cleanup on the url for the final get command, but it seems to work this way.

Error in getting access token using client_credentials and authorization_code grant_type

We need to consume API in Android application which requires access token. Tried client_credentials and authorization_code method to get the access token.
Getting the following error for client_credentials:
"AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid.
Note: We are passing client_id,client_secret,scope and grant_type in the body of the request.
grant_type:client_credentials
client_id:*
client_secret:*
scope:****
The second method which we tried "authorization_code" is returning 200 OK Response without any authorization token.
grant_type:authorization_code
client_id:*
client_secret:
scope:
redirect_uri:*
state:12345
response_type:code
response_mode:query
Any pointers on this will be greatly appreciated
You should try passing scope too along with grant_type etc.
Try
**'client_secret' : 'xxxxxxxxxxxxxxxxxxxxxxxx',
'client_id':'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'scope':'https://graph.microsoft.com/.default',
'grant_type':"client_credentials"**
Try with these parameters and see if it works.
Although in the documentation somewhere is recommended/options but you have to pass scope in the body along with other parameters.
Thanks

Can't deploy an app to Intune store via graph API - DeviceManagementApps.ReadWrite.All is an invalid scope?

We want to enable uploading apps to the Intune store via an API.
I saw this example on GitHub, and want to do something similar in JS, so I've tried using the same REST calls.
The problem is, I can't seem to make the https://graph.microsoft.com/beta/deviceAppManagement/mobileApps request properly - I always get 401. When making the same request via the Graph API Explorer it works fine.
I tried fixing my permissions, and I'm kinda stuck getting the correct token.
I did the following steps with an admin account, on both the "common" and our own tennant:
Called the admin consent - https://login.microsoftonline.com/nativeflow.onmicrosoft.com/adminconsent?client_id=<ID>&redirect_uri=<URI>
Got authorization from the user - https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=<ID>&response_type=code&redirect_uri=<URI>&response_mode=query&scope=DeviceManagementApps.ReadWrite.All
POST request to get the actual token -
https://login.microsoftonline.com/nativeflow.onmicrosoft.com/oauth2/v2.0/token
with the following body:
client_id: <ID>
scope: https://graph.microsoft.com/.default
client_secret: <secret>
grant_type: client_credentials
requested_token_use: on_behalf_of
code: <The code I got in step 2>
I tried changing the scope in step 3 to https://graph.microsoft.com/DeviceManagementApps.ReadWrite.All or simply to DeviceManagementApps.ReadWrite.All, but it says that it's not a valid scope.
I got a token in step 3, but when I try calling the actual API I receive this error:
{
ErrorCode:"Forbidden",
Message:{
_version: 3,
Message: "An error has occurred - Operation ID (for customer support): 00000000-0000-0000-0000-000000000000 - Activity ID: 7b5c3841-976d-4509-b946-f7fdabd047d7 - Url: https://fef.msub02.manage.microsoft.com/StatelessAppMetadataFEService/deviceAppManagement/mobileApps?api-version=5018-05-02",
CustomApiErrorPhrase: "",
RetryAfter: null,
ErrorSourceService: "",
HttpHeaders: {"WWW-Authenticate":"Bearer realm=urn:intune:service,f0f3c450-59bf-4f0d-b1b2-0ef84ddfe3c7"}
},
Target:null,
Details:null,
InnerError:null,
InstanceAnnotations:[]
}
So yeah, I'm pretty much stuck. Anyone have any experience with it? I've tried making the calls in Postman, curl and via code, but nothing works.
Cheers :)
You have a couple issues going on:
You're using the Authorization Code Grant workflow but requesting Client Credentials.
The scope Device.ReadWrite.All is an application scope, it is only applicable to Client Credentials. It isn't a valid Delegated scope so it will return an error when you attempt to authenticate a user (aka delegate) using Device.ReadWrite.All.
Your body is using key:value but it should be using standard form encoding (key=value).
To get this working, you need to request a token without a user. This is done by skipping your 2nd step and moving directly to retrieving a token (body line-breaks are only for readability):
POST https://login.microsoftonline.com/nativeflow.onmicrosoft.com/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={id}
&client_secret={secret}
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&grant_type=client_credentials

fetch_access_token! in Google API -- "Missing authorization code."

I'm using the Google API Ruby Client get get access to users' calendars.
I get access with:
client_id: "xxxxx"
client_secret: "xxxxx"
access_type: "offline"
approval_type: ""
scope: "https://www.google.com/calendar/feeds/ https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/calendar"
callback_path: "/app/oauth_response"
provider :google_oauth2, GOOGLE_API['client_id'], GOOGLE_API['client_secret'],
{ access_type: GOOGLE_API['access_type'],
approval_prompt: GOOGLE_API['approval_prompt'],
scope: GOOGLE_API['scope'],
callback_path: GOOGLE_API['callback_path'],
path_prefix: GOOGLE_API['path_prefix']}
When the response comes back it has a refresh token, access token, expired_at, etc. I am then able to make API requests with the access code. But once that access code expires (after an hour), I believe I need to use the refresh token to get a new access token, correct?
Here's the call I'm making:
HTTParty.get('https://www.google.com/calendar/feeds/default/owncalendars/full', :query => {:alt => "jsonc", :access_token => access_token})
Which tells me that my token expired. So I try to get a new one.
But when I try to do that I get this:
#client.authorization.fetch_access_token!
ArgumentError Exception: Missing authorization code.
I see that in my #client object, #code=nil. I'm assuming that's what needs to be set, but I don't get a 'code' property returned from my initial request.
How do I get that code, or if I don't need it, what am I doing wrong?
Thanks!
Most likely the the refresh token isn't set in #client.authorization at the point you're calling fetch_access_token!
Take a look at https://github.com/google/signet/blob/master/lib/signet/oauth_2/client.rb
That error message only appears in the fall through for an unknown/unspecified grant_type. grant_type itself is inferred based on the state of the authorization client.
In your use case, trying to refresh a token, it should be 'refresh_token' and grant_type will return that value if a refresh_token is set. My hunch is if you dump the value of #client.authorization.refresh_token and #client.authorization.grant_type they'll both be nil.
The fix would be to just to make sure you properly restore the refresh_token prior to calling that method.
The Ruby client library takes care of automatically getting a new access token from the refresh token when the former expires, so you don't need to do anything to handle that case.
In the client there is following code (https://github.com/google/signet/blob/621515ddeec1dfb6aef662cdfaca7ab30e90e5a1/lib/signet/oauth_2/client.rb#L935), so you need to remove aredirect_uri, when you do not want to get access token.
if self.redirect_uri
# Grant type was intended to be `authorization_code` because of
# the presence of the redirect URI.
raise ArgumentError, 'Missing authorization code.'
end

Resources