Context: GMail Add-on code with Library resource OAuth2 at version 29.
I have this code working (adjusted to show here):
function getOAuthService() {
return OAuth2.createService('myName')
.setAuthorizationBaseUrl(baseUrl)
.setTokenUrl(tokenUrl)
.setTokenFormat('application/x-www-form-urlencoded')
.setClientId('myId')
.setClientSecret(mySecret)
.setScope('myScope') // arbitrary text
.setCallbackFunction('authCallback')
.setCache(CacheService.getUserCache())
.setPropertyStore(PropertiesService.getUserProperties());
}
The callback function is like this:
function authCallback(request) {
var service = getOAuthService();
var authorized = service.handleCallback(request);
console.log(authorized); // --> true
console.log(service.getAccessToken()); // --> undefined
console.log(JSON.stringify(service.getToken())); // --> see below
console.log(service.getToken()); // --> see below
}
The result of the stringified getToken() call is a mess:
{"{\"access_token\":\"IqINbsrIchvc2bV27smQfAy5ldn6s6iJ9z9LPrUx/pJu+j9yJhb1MID/WBeVKrlvzvUrTPdcHdyhYz9CX0WcxLxbtCBU8wk6LdYQ8rRV5/XBE4XsU5Ik4wQNiItOimQf2f1V3VVuNSP/n50LCqVmQG0pNv/d5dUWnvq1OvSSJX9CvyY8RHioCsSn8pt/PTE4GYWy8R0/9wR2HbmIqjaDgg":"","granted_time":1534261033}
and the non-stringified output looks like
{granted_time=1.534261033E9, {"access_token":"IqINbsrIchvc2bV27smQfAy5ldn6s6iJ9z9LPrUx/pJu+j9yJhb1MID/WBeVKrlvzvUrTPdcHdyhYz9CX0WcxLxbtCBU8wk6LdYQ8rRV5/XBE4XsU5Ik4wQNiItOimQf2f1V3VVuNSP/n50LCqVmQG0pNv/d5dUWnvq1OvSSJX9CvyY8RHioCsSn8pt/PTE4GYWy8R0/9wR2HbmIqjaDgg=}
The remote authorization service is returning this JSON when it is called:
{
"access_token": "IqINbsrIchvc2bV27smQfP/PFXNLCvkfwNRFXoBX4QYH+WojfWNTi1GUdxUKWdaYPbblrAHQnAHgWRSb0iGn1pNIMQVwOYZb6KMY3eZCnxjHwC5lkotEmHdnLX5A2DihiAqaZy3w8ROc0pXafvl+7+E/meWWCnQdKXVEoc/7S3DD49yK0sNqTcA9AOdFrmxB4+SA3d56O6Zh6oOIx4NGug==",
"token_type": "bearer",
"expires_in": 3600
}
How do I get this to work correctly and give back the access token?
Related
I want to create a new own route API using admin API. I tried this code :
<?php declare(strict_types=1);
namespace TestApi\Controller\Api;
use Shopware\Core\Framework\Context;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
* #RouteScope(scopes={"api"})
*/
class ApiController extends AbstractController
{
protected EntityRepositoryInterface $productRepository;
public function __construct(EntityRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
/**
* #Route("/api/product", name="api.product.search", methods={"GET"})
*/
public function getProducts(Context $context): JsonResponse
{
$criteria = new Criteria();
return new JsonResponse($this->productRepository->search($criteria, $context));
}
}
When I try this request {baseUrl}/api/product in storefront I get this error:
{"errors":[{"code":"9","status":"401","title":"The resource owner or authorization server denied the request.","detail":"Missing \u0022Authorization\u0022 header","meta":{"trace":[{"file":"\/var\/www\/html\/vendor\/league\/oauth2-server\/src\/AuthorizationValidators\/BearerTokenValidator.php","line":93,"function":"accessDenied","class":"League\\OAuth2\\Server\\Exception\\OAuthServerException","type":"::","args":["Missing \u0022Authorization\u0022 header"]}
Could you help me please ?
You'll have to provide the Authorization header in the request to your admin-api endpoint. The header should include a valid token. To retrieve the token you must first request the corresponding endpoint:
// POST /api/oauth/token
{
"grant_type": "client_credentials",
"client_id": "...",
"client_secret": "..."
}
You'll get a client_id and client_secret by creating an integration in the administration of your shop.
This endpoint will then return a temporarily valid token:
{
"token_type": "Bearer",
"expires_in": 600,
"access_token": "xxxxxxxxxxxxxx"
}
You then take the access_token and in all your following requests to the api you set it for Authorization in the request header, prepended by Bearer:
Authorization: Bearer xxxxxxxxxxxxxx
If you're using a javascript client like axios the object for the headers then would look like this for example:
{
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
There are two API's, one for Admin use (starts with /api/), one for Storefront (/store-api/). As you can see, you are not using the storefront API, and therefore expected to provide the appropriate Bearer token.
Moreover, please check that /api/product route already exists in the admin API.
Another thing, I think there is some confusion between a controller & an API route implementation. Maybe you could follow the official documentation on it or look it up online?
I have implemented the new Google Identity Services to get an access_token to call the Youtube API.
I try to use this on an Angular app.
this.tokenClient = google.accounts.oauth2.initTokenClient({
client_id: googleApiClientId,
scope: 'https://www.googleapis.com/auth/youtube.readonly',
callback: (tokenResponse) => {
this.accessToken = tokenResponse.access_token;
},
});
When I call this.tokenClient.requestAccessToken(), I can get an access token and use the Youtube API, that works.
But after one hour, this token expires. I have this error : "Request had invalid authentication credentials."
How can I get the newly refreshed access_token transparently for the user ?
There are two authorization flows for the Google Identity Services (GIS) library:
The implicit flow, which is client-side only and uses .requestAccessToken()
The authorization code flow, which requires a backend (server-side) as well and uses .requestCode()
With the implicit flow (which is what you are using), there are no refresh tokens. It is up to the client to detect tokens aging out and to re-run the token request flow. Here is some sample code from google's examples for how to handle this:
// initialize the client
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
prompt: 'consent',
callback: '', // defined at request time in await/promise scope.
});
// handler for when token expires
async function getToken(err) {
if (err.result.error.code == 401 || (err.result.error.code == 403) &&
(err.result.error.status == "PERMISSION_DENIED")) {
// The access token is missing, invalid, or expired, prompt for user consent to obtain one.
await new Promise((resolve, reject) => {
try {
// Settle this promise in the response callback for requestAccessToken()
tokenClient.callback = (resp) => {
if (resp.error !== undefined) {
reject(resp);
}
// GIS has automatically updated gapi.client with the newly issued access token.
console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));
resolve(resp);
};
tokenClient.requestAccessToken();
} catch (err) {
console.log(err)
}
});
} else {
// Errors unrelated to authorization: server errors, exceeding quota, bad requests, and so on.
throw new Error(err);
}
}
// make the request
function showEvents() {
// Try to fetch a list of Calendar events. If a valid access token is needed,
// prompt to obtain one and then retry the original request.
gapi.client.calendar.events.list({ 'calendarId': 'primary' })
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => getToken(err)) // for authorization errors obtain an access token
.then(retry => gapi.client.calendar.events.list({ 'calendarId': 'primary' }))
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => console.log(err)); // cancelled by user, timeout, etc.
}
Unfortunately GIS doesn't handle any of the token refreshing for you the way that GAPI did, so you will probably want to wrap your access in some common retry logic.
The important bits are that the status code will be a 401 or 403 and the status will be PERMISSION_DENIED.
You can see the details of this example here, toggle to the async/await tab to see the full code.
To refresh the access token in a transparent way for the end-user you have to use the Refresh Token, This token will also come in the response to your call.
With this token, you can do a POST call to the URL: https://www.googleapis.com/oauth2/v4/token with the following request body
client_id: <YOUR_CLIENT_ID>
client_secret: <YOUR_CLIENT_SECRET>
refresh_token: <REFRESH_TOKEN_FOR_THE_USER>
grant_type: refresh_token
refresh token never expires so you can use it any number of times. The response will be a JSON like this:
{
"access_token": "your refreshed access token",
"expires_in": 3599,
"scope": "Set of scope which you have given",
"token_type": "Bearer"
}
#victor-navarro's answer is correct, but I think the URL is wrong.
I made a POST call to https://oauth2.googleapis.com/token with a body like this and it worked for me:
client_id: <YOUR_CLIENT_ID>
client_secret: <YOUR_CLIENT_SECRET>
refresh_token: <REFRESH_TOKEN_FOR_THE_USER>
grant_type: refresh_token
I know some will put comment like this post is duplicate of so many questions, but I've tried many ways to achieve Access Token in linkedin Oauth. Explaining what i tried.
1) I'm following it's official doc's Linkedin Oauth2
2) I'm successfully getting Authorization code from step 2 and passing that code to step 3 for exchanging Auth code for getting Access Token. But i'm getting following error
{"error_description":"missing required parameters, includes an invalid parameter value, parameter more than once. : Unable to retrieve access token : appId or redirect uri does not match authorization code or authorization code expired","error":"invalid_request"}
3) According to some links i need to set content-type in the header.Link which tells to set content-type is missing
4)Then i tried calling https://www.linkedin.com/uas/oauth2/accessToken this service instead of POSt to GET. And passing data as queryParams.
5) Some link says oauth code expires in 20 sec, So i've checked, i'm making call for access token in less that 1 sec.
6) And if i pass data in Body params like as below and used url as https://www.linkedin.com/uas/oauth2/accessToken
var postData = {
grant_type: "authorization_code",
code: authCode,
redirect_uri: 'https%3A%2F%2Foauthtest-mydeployed-app-url',
client_id: 'my_client_id',
client_secret: 'secret_key'
};
7) With Get call my url i tried
https://www.linkedin.com/uas/oauth2/accessToken?grant_type=authorization_code&code='+authCode+'&redirect_uri=https%3A%2F%2Foauthtest-mydeployed-app-url&client_id=my_client_id&client_secret=secret_key
Still i'm getting Error even though status code is 200, i'm getting that error(with GET api)
and If POSt by passing postData in body i'm getting bad request 400 status code
Not understanding why m I not getting access code. I've read many solutions.
Sharing code as requested.
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function (Controller, MessageToast) {
"use strict";
return Controller.extend("OauthTest.OauthTest.controller.View1", {
onPress: function (evt) {
var sPath =
'https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=my_client_id&redirect_uri=https%3A%2F%2Foauthtest-mydeployed-app-url&state=DCEeFWf45A53sdfKef424&scope=r_basicprofile';
window.location.href = sPath;
var oRouter = new sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("View2", {
"username": "Test"
});
MessageToast.show(evt.getSource().getId() + " Pressed");
},
//after user allows access, user will be redirected to this app with code and state in URL
//i'm fetching code from URL in below method(call is happening in max.569ms)
onAfterRendering: function () {
var currentUrl = window.location.href;
var url = new URL(currentUrl);
var authCode = url.searchParams.get("code");
if (authCode !== undefined && authCode !== null) {
var postData = {
grant_type: "authorization_code",
code: authCode,
redirect_uri: 'https%3A%2F%2Foauthtest-mydeployed-app-url',
client_id: 'my_client_id',
client_secret: 'secret_key'
};
/* var accessTokenUrl = 'https://www.linkedin.com/uas/oauth2/accessToken?grant_type=authorization_code&code=' + authCode +'&redirect_uri=https%3A%2F%2Foauthtest-mydeployed-app-url&client_id=my_client_id&client_secret=secret_key';*/
var accessTokenUrl = 'https://www.linkedin.com/uas/oauth2/accessToken';
$.ajax({
url: accessTokenUrl,
type: "POST",
beforeSend: function (xhr) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
},
data: postData,
success: function (data, textStatus, jqXHR) {
console.log(data);
alert('success');
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(errorThrown);
alert('error');
}
});
}
}
});
});
Help will be appriciated..!!!
Finally I am happy to post my answer after so much search.
Every step I did is correct only, but one thing I was missing here like, Linkedin API doesn't supports CORS.
I tried implementing Javascript SDK, that works like charm. But API wasn't.
Then I found very helpful Link which says I need to implement Rest API from backend by allowing CORS, not from front end.
Make sure to follow all the points which I mentioned above in my post.
And for Allow CORS follow this link. You will get data but only basic profile of user according to LinkedIn Terms data can be accessible
Hope this post may help someones time to search more
I am trying to implement OAuth2 authentication in a Dropwizard web-application. I have created the required Authenticator and Authorizer classes and added the code supplied in the Dropwizard manual in my Application's run-method as follows:
environment.jersey().register(new AuthDynamicFeature(
new OAuthCredentialAuthFilter.Builder<User>()
.setAuthenticator(new TokenAuthenticator(service))
.setAuthorizer(new TokenAuthorizer())
.setPrefix("Bearer")
.buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use #Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
My required behavior is that after my client has logged in by providing his/her credentials on my login page, I want to redirect the client to a greeting page I have created using Dropwizard Views and is under the path: "/me" as follows:
//After succesfull login and token generation
return Response.seeOther(new URI("/me")).build(); // redirect to greeting page
And my greeting resource looks as follows:
#Path("/me")
#Produces(MediaType.TEXT_HTML)
public class UserResource {
#GET
public UserView getView(#Auth User user) {
return new UserView(user);
}
}
Currently I am getting a "Credentials are required to access this resource." response after logging in. After some reading on token authentication (nice explanation here) I picked up that the token must be sent from the client in the header of each request. So my question is how do I tell the user's browser (client) to include the token in the header of future requests?
I managed to solve this by doing the following:
In order to verify the user, a token must be sent in the header of the request in the form of Authorization: Bearer <token-value>. This token is sent by the server upon authentication and must be stored by the client / user to be sent in future requests. I managed to store the token by using an ajax request when my login form is submitted as follows:
<#-- Handle form submission response to save the token on the client side-->
<script>
$('#loginForm').submit(function(event){
event.preventDefault();
$.ajax({
url: $(this).attr('action'),
type: 'POST',
data : $(this).serialize(),
dataType: 'json',
success: function(data){
//alert("The server says success!!: " +data);
console.log(data);
window.sessionStorage.accessToken = data.token;
window.location = data.url;
},
error: function(data){
alert("The server says error! : ");
console.log(data);
}
});
});
</script>
The login resource then produces JSON which is received in the data-variable in the code above. The required token resides in data.token - which is then stored. I added a second entry in the JSON named "url" to indicate the path to redirect to after successful authentication.
Now the token is stored on the client side when needed. In order to send this token in the request header, I needed to alter my approach to using the Views provided by Dropwizard. Instead of directly requiring authentication, I split the View's resource and the authenticated data resources. To clarify consider the following example. A user logs in, gets the token and then goes to a page that displays his/her username. For the page, a View resource is created with a .ftl file to serve as a template. Something like:
#Path("/me")
#Produces(MediaType.TEXT_HTML)
public class UserResource {
#GET
public UserView getView() {
return new UserView();
}
}
and...
public class UserView extends View {
public UserView() {
super("user.ftl");
}
}
And user.ftl:
<#include "include/head.html">
<#include "include/header.html">
<!-- Header -->
<div id ="headerWrapper">
</div>
<div class="container-fluid">
<div id="name">
<p>Hello user</p>
</div>
</div>
<#include "include/footer.html">
Now to retrieve the username I create a new resource which produces JSON on a new path. For example:
#Path("/getdetails")
#Produces(MediaType.APPLICATION_JSON)
public class UserDetailsResource {
#GET
#Timed
#UnitOfWork
public User getDetails(#Auth User user) {
return user;
}
}
This resource requires authentication and provides JSON from which the username can be retrieved. Now to get the username and place it inside the view, simply add a script to the users.ftl with an ajax request to the getdetails resource, providing the token in the header and using the result to place the username in the view. See script below.
<script>
$.ajax({
url: '/getdetails',
type: 'GET',
headers: {"Authorization": window.sessionStorage.accessToken},
dataType: 'json',
success: function(data){
//alert("The server says success!!: " +data);
console.log(data);
$("#name").text('Hello '+data.username);
},
error: function(data){
alert("The server says error! : ");
console.log(data);
}
});
</script>
I'm new to office 365 and having problem with accessing rest api.
I'm trying to test the rest api of Calendar and Mail API, so I decided to use Postman. However, to test those APIs, I need an access token in Authorization header. To figure out how to get a token, I decided to get the sample project here , configure, run and sign in on this local site to get the token cached in local storage and use that token for further requests in Postman. However, all requests I tested returned '401 unauthorized request'.
What I did:
Register a new app on Azure ADD associated with O365 account
Add full app permissions and delegated permissions.
Update 'oauth2AllowImplicitFlow' to true in manifest file.
Clone sample project
In app.js, I change the alter the content of config function as following
function config($routeProvider, $httpProvider, adalAuthenticationServiceProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: 'HomeController',
controllerAs: 'home',
requireADLogin: true
})
.otherwise({
redirectTo: '/'
});
// The endpoints here are resources for ADAL to get tokens for.
var endpoints = {
'https://outlook.office365.com': 'https://outlook.office365.com'
};
// Initialize the ADAL provider with your tenant name and clientID (found in the Azure Management Portal).
adalAuthenticationServiceProvider.init(
{
tenant: 'mytenantname.onmicrosoft.com',
clientId: '<my cliend Id>',
endpoints: endpoints,
cacheLocation: 'localStorage'
},
$httpProvider
);
};
Then I ran the app, it sign me in just fine and I can also get the token, but that token is also unauthorized to request.
I decoded the token and saw the value of 'aud', it didn't return "https://outlook.office365.com/". In this url, the author said that "This should be "https://outlook.office365.com/" for the Mail, Calendar, or Contacts APIs"
So what did I miss ?
How you call the Office 365 API in AngularJS?
When signing the user in, you will only get the id_token to authenticate the user.
The aud of id_token is the tenant id (GUID).
To call the Office 365 API, you need to use AugularJS http request.
Here is a sample of sending email using Microsoft Graph API in AngularJS:
// Build the HTTP request to send an email.
var request = {
method: 'POST',
url: 'https://graph.microsoft.com/v1.0/me/microsoft.graph.sendmail',
data: email
};
// Execute the HTTP request.
$http(request)
.then(function (response) {
$log.debug('HTTP request to Microsoft Graph API returned successfully.', response);
response.status === 202 ? vm.requestSuccess = true : vm.requestSuccess = false;
vm.requestFinished = true;
}, function (error) {
$log.error('HTTP request to Microsoft Graph API failed.');
vm.requestSuccess= false;
vm.requestFinished = true;
});
Before calling the API, ADAL.js will acquire another token - access token which you can used to send the email.
UPDATE#1
I also downloaded the sample you mentioned. To run this sample, please ensure you have the Exchange Online > Read and writer user mail Permission assigned in your application.