I am getting an error when I try to refresh access token:
400 Bad Request
{error : "unauthorized_client"}
From the Google token URI:
{
"error" : "invalid_request"
}
I read this answer here and the official Google documentation (which describes how a POST request should look) and I don't see any difference.
I captured my POST request (secrets removed):
POST /SHOWMERAWPOST HTTP/1.1
User-Agent: Google-HTTP-Java-Client/1.10.3-beta (gzip)
Pragma: no-cache
Host: requestb.in
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 175
Connection: keep-alive
Cache-Control: no-cache
Accept-Encoding: gzip
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
grant_type=refresh_token&refresh_token=******&client_id=*******.apps.googleusercontent.com&client_secret=******
Java code which sends the request:
RefreshTokenRequest req = new RefreshTokenRequest(new NetHttpTransport(), new JacksonFactory(), new GenericUrl(
getSecrets().getDetails().getTokenUri()), REFRESH_TOKEN);
req.set("client_id", getSecrets().getDetails().getClientId());
req.set("client_secret", getSecrets().getDetails().getClientSecret());
TokenResponse response = req.execute();
Is there anything wrong?
PROBLEM EXPLANATION
With the hint #MartinV gave I was finally able to fix it! Because his answer doesn't explain very well how to solve it, I'm going to post it here.
The problem is because we all have generated the Refresh Token using Google's OAuth Playground, but when you click 'Authorize APIs' in the first step, it takes you to the concent screen using the Playground app. After that, all the tokens that you create can be used only by the Playground app, but of course you don't know either the Client ID or the Client Secret for that app.
SOLUTION
The solution is to make Playground to use your own Client ID and Secret. To do so, click on the Settings button:
And enter your Client ID and Secret. But, before you do that, as it says there, you need to go to the Developer's Console, find your OAuth 2.0 client IDs client, edit it and add https://developers.google.com/oauthplayground under Authorized redirect URIs. After you added that and saved the changes, go back to the playground and try to Authorize APIs. In my case it took like 15 minutes before the changes in the Authorized redirect URIs took effect.
Once you're done, don't forget to remove the Playground URI from the Developer Console!
EXTRA
Once I have done that, in Python I did this and it worked:
access_token = None
client_id = 'xxxxxxxx.apps.googleusercontent.com'
client_secret = 'xxxxxxxxxxxx'
refresh_token = 'xxxxxxxxxxxx'
token_expiry = None
token_uri = "https://accounts.google.com/o/oauth2/token"
user_agent = 'YourAgent/1.0'
credentials = client.GoogleCredentials(access_token, client_id, client_secret, refresh_token, token_expiry, token_uri, user_agent)
http = credentials.authorize(httplib2.Http())
credentials.refresh(http)
service = build('drive', 'v3', http=http)
req = service.files().list()
resp = req.execute(http=http)
I created access and refresh token in OAuth2 playground and then i copied them to my app.
It`s not allowed to have different clients for autorization and for token refresh.
Another solution using the REST API to get an access_token and then use it to interact with the REST API (e.g. add a video to a private playlist) after creating the refresh_token as described above.
import requests
import json
# according to https://stackoverflow.com/a/41556775/3774227
client_id = '<client_id>'
client_secret = '<client_secret>'
refresh_token = '<refresh_token>'
playlist_id = '<playlist>'
video_id = 'M7FIvfx5J10'
def get_access_token(client_id, client_secret, refresh_token):
url = 'https://www.googleapis.com/oauth2/v4/token'
data = {
'client_id': client_id,
'client_secret': client_secret,
'refresh_token': refresh_token,
'grant_type': 'refresh_token'
}
response = requests.post(
url=url,
data=data,
)
return response.json().get('access_token')
def add_video_to_playlist(playlist_id, video_id, access_token):
url = 'https://www.googleapis.com/youtube/v3/playlistItems'
params = {
'part': 'snippet',
}
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer {}'.format(access_token)
}
data = {
'snippet': {
'playlistId': playlist_id,
'resourceId': {
'kind': 'youtube#video',
'videoId': video_id
},
}
}
requests.post(
url=url,
params=params,
headers=headers,
data=json.dumps(data)
)
if __name__ == '__main__':
access_token = get_access_token(client_id, client_secret, refresh_token)
add_video_to_playlist(playlist_id, video_id, access_token)
I had the same problem. The solution was to use the same client when authorizing in the application and when updating the token on the server.
Can't refresh access token for Google Calendar API on server side
Related
I am new to Svelte, and am trying to create a login page to an API. The API takes a username and password and returns an Authorization header. I see the authorization header in the F12 developer console, and I am able to access other headers via code, but not the Authorization header. I have enabled CORS on the server for localhost:8080.
<script>
const BASE_URL = ...;
export let username;
export let password;
let result;
let status;
let body;
let token;
let contentType;
async function doPost () {
const res = await fetch(BASE_URL + 'authenticate', {
method: 'POST',
mode: 'cors',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
'username': username,
'password': password
})
});
const text = await res.text();
status = res.status;
result = text;
token = res.headers.get('Authorization');
contentType = res.headers.get('Content-type');
if (!res.ok) {
throw new Error(result);
}
}
</script>
Please log in<br>
<input type="text" bind:value={username}/>
<br>
<input type="password" bind:value={password}/>
<br>
<button type="button" on:click={doPost}>Log in</button>
<br>
Result: {result}
<br>
Status: {status}
<br>
Token: {token}
<br>
Content-type: {contentType}
Response headers are as follows:
HTTP/1.1 200
Server: nginx/1.20.0
Date: Tue, 31 May 2022 18:59:09 GMT
Content-Type: text/plain;charset=UTF-8
Content-Length: 8
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:8080
Authorization: Bearer xyz...
The page displays as follows after logging in:
Result: Welcome!
Status: 200
Token: null
Content-type: text/plain;charset=UTF-8
Server side (spring boot) has the following annotation on the authenticate method:
#CrossOrigin(origins = "http://localhost:8080", allowedHeaders = "*", allowCredentials = "true")
As you can see, I am able to access the content-type header but not the authorization header. Any assistance would be greatly appreciated.
I figured it out. I needed to add exposedHeaders = "Authorization" to the #CrossOrigin annotation on the server side.
I literally solved this exact problem today. So... I'm not entirely sure why you cannot access the cookies sent back in the HTTP response, I believe it has something to do with not allowing js access to cookie related data for security reasons.
A preliminary issue I see, is that you should be sending the API auth token to the frontend, in the 'set-cookie' header, along with sending it in the HTTP response body, which I assume is JSON for your API.
I've never seen anyone suggest sending it in the 'Authorization' header like you have. I believe you are confused. I'll try and clarify the right way to do this and why you're most likely confused.
Your backend will generate an access token of some sort upon a successful login. Like I said, you send it back in the 'set-cookie' header, aswell as in the HTTP body.
Now when you read the response on the frontend, you can retrieve the Auth token from the HTTP response body, and use it in subsequent requests to authenticate to your backend server. The way JWT tokens are expected to be sent is in the 'Authorization' header of your request. This is where you're mixed up, the 'Authorization' header is used in subsequent authenticated requests to the server, not to send the Auth token from the backend to the frontend.
Now along with setting up the 'Authorization' header, you'll most likely need to send that same token in the 'cookie' header. You can do this by using the {withCredentials: true} option with fetch. This will send the cookie you sent in the 'set-cookie' response header after a successful login attempt, back to the server on all subsequent requests where you set this option.
Hope this helps, sorry I'm on my phone, so restricted with what I can write.
I'm new to using the rest-client. I know I'm missing something, but I am trying to do the following:
Post to a login endpoint to authenticate
After authentication, post csv text to another endpoint that requires a logged in user
The authentication portion is successful, however, I am getting a 401 Unauthorized when step 2 occurs.
rest_client = RestClient
login_response = #global_rest_client.post(
host + 'LOGIN ENDPOINT',
{ userName: 'user', password: 'password'},
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
)
import_response = rest_client.post(
host + 'IMPORT DATA ENDPOINT',
headers: { 'X-System-Name': 'AndroidMobile', 'Content-Type': 'multipart/form-data },
csv: csv_string
)
My understanding of how authentication works could be wrong. My assumption is that as long as the same instance of the client has a successful login, then the post of csv data would also be successful.
I appreciate any input.
HTTP (1.1) is stateless so a request does not contain any information about previous requests unless that information is encoded and added to the request in some way (e.g. cookies or headers). So when you make your import request the server does not know if/that you are authenticated even though you just made a login request.
You'll have to include the token you receive from your login request in subsequent requests. This should go in the 'Authorization' header.
For example:
auth_token = login_response["success"]["token"] # or whatever the key is for the token
import_response = rest_client.post(
host + 'IMPORT DATA ENDPOINT',
headers: { 'Authorization': "Bearer #{auth_token}", 'X-System-Name': 'AndroidMobile', 'Content-Type': 'multipart/form-data },
csv: csv_string
)
The way authentication works depends on the server and can be different in different cases. So the site you are accessing might expect the Authorization header to be like "Token #{auth_token}" or anything else, but they should mention it in their documentation.
I have a Django app that authenticates using Okta:
headers = {
'Authorization': 'SSWS {}'.format(<okta api token>),
'Accept': 'application/json',
'Content-Type': 'application/json'
}
authentication_payload = {
'username': <username>,
'password': <password>
}
response = requests.post(
<okta auth endpoint>,
headers=headers,
data=json.dumps(authentication_payload)
)
This works successfully. From the response content I am able to get the User Id:
content = json.loads(r.content.decode('utf-8'))
okta_user_id = content['_embedded']['user']['id']
I then use the okta_user_id to create the endpoint to get the okta user by id:
okta_user_endpoint = https://<org>.okta.com/api/v1/users/<okta_user_id>
I then use the same headers from the authentication call, with the same api token, and try to get the user by id:
user_response = requests.get(
okta_user_endpoint,
headers=headers
)
But this is unsuccessful. I get a 401 error with the following content:
{
"errorCode":"E0000011",
"errorSummary":"Invalid token provided",
"errorLink":"E0000011",
"errorCauses":[]
}
Seems straight forward with an invalid token, but if the token is invalid how am I able to successfully make the authentication call? And if the token if valid for the authentication call why is it not working to get the user by id?
Okta recently changed the way that the /authn endpoint works. The /authn endpoint no longer requires an authentication token. This was done in order to support single-page applications.
It looks like your application will need to be able to fetch user information on an arbitrary user. In that case, using an Okta API token makes sense.
However, if you were making that call from a single-page application, you would want to make a request to the /users/me API endpoint.
How can I get security token from uaa server with user name and password. Can you give me working examples that I can use from Python and/or Postman to do three following things:
1. Log in.
2. Get the token.
3. Validate the token.
the endpoints from these uaa API docs do not work:
https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst#verify-user-get-users-id-verify
Python:
import requests, json, jwt
client_auth = requests.auth.HTTPBasicAuth('app', 'appclientsecret')
url = 'http://localhost:8080/uaa/oauth/token'
retval = requests.post(url=url, headers={'accept':'application/json'}, params= {'username':'marissa','password':'koala', 'grant_type':'password','client_id':'app'}, auth=client_auth)
decode_token(json.loads(retval.content.decode('utf-8'))['access_token'])
def decode_token(token):
print(token)
docoded = jwt.decode(token, verify=False)
print(decoded)
Postman:
POST /uaa/oauth/token? username=marissa&password=koala&client_id=app&grant_type=password HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Authorization: Basic YXBwOmFwcGNsaWVudHNlY3JldA==
Cache-Control: no-cache
I am trying to exchange an OAuth one-time use code that I got from my client-side app into a access token and refresh token on my server. The response that I get is:
{
"error" : "redirect_uri_mismatch"
}
My POST request is:
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
code={My Code}&
client_id={My Client ID}&
client_secret={My Client Secret}&
grant_type=authorization_code
I have checked my Client ID and Client Secret against those in the API Console and they match.
I get the one-time use code on my client with the following Java code:
static final List<String> SCOPES = Arrays.asList(new String[]{"https://www.googleapis.com/auth/plus.login","https://www.googleapis.com/auth/userinfo.email"});
String scope = String.format("oauth2:server:client_id:%s:api_scope:%s", SERVER_CLIENT_ID, TextUtils.join(" ", SCOPES));
final String token = GoogleAuthUtil.getToken(c, email, scope);
I have a redirect_uri in my API Console, but since I am trying to use cross-client authorization (as described here), I deliberately left it out of the POST request as is required:
When it exchanges the code for tokens, it should not include the “redirect_uri” argument in the POST.
Any idea on what I am doing wrong?
It turns out that "should not include the 'redirect_uri' argument in the POST" does not mean to completely omit the redirect_uri field. It instead means that the redirect_uri field should have an empty value.
My new, working POST is:
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
code={My Code}&
client_id={My Client ID}&
client_secret={My Client Secret}&
redirect_uri=''&
grant_type=authorization_code
In my case the following - https://stackoverflow.com/a/25184995/775359 - answer was helpful:
var post_data = {
code : code,
client_id : google_client_id,
client_secret : google_client_secret,
redirect_uri : 'postmessage',
grant_type : grant_type,
};
Setting redirect_uri to postmessage solved my issue with redirect_uri mismatch.
EDIT: Another answer referencing postmessage: https://stackoverflow.com/a/18990268/775359