Accessing Shoeboxed API with Google Apps Script (OAuth v2) - api

I'm trying to initiate a session with the Shoeboxed API via Google Apps Script. I hoped I could use Apps Script internal library to access it but I'm having issues. Here is my attempt:
function testAPI() {
var consumerKey = '';
var consumerSecret = '';
var oauthConfig = UrlFetchApp.addOAuthService('shoeboxed');
oauthConfig.setAccessTokenUrl(
'https://id.shoeboxed.com/oauth/token');
oauthConfig.setRequestTokenUrl(
'https://id.shoeboxed.com/oauth/token');
oauthConfig.setAuthorizationUrl(
'https://id.shoeboxed.com/oauth/authorize');
oauthConfig.setConsumerKey(consumerKey);
oauthConfig.setConsumerSecret(consumerSecret);
var options = {
'oAuthServiceName' : 'shoeboxed',
'oAuthUseToken' : 'always'
};
var url = 'https://api.shoeboxed.com/v2/user';
var response = UrlFetchApp.fetch(url, options);
Logger.log("Response: " + response.getContentText());
}
It's failing at the point where it attempts to fetch user data via the API url with an authorization failed message. I'm not sure what I'm doing wrong. Information about the API and OAuth can be found here: https://github.com/Shoeboxed/api/blob/master/sections/authentication.md

New method:
It looks like that API requires OAuth2, but the UrlFetchApp.addOAuthService method only works with the older version of OAuth.
There's a new method ScriptApp.newStateToken() which can be used in combination with OAuth2, but it requires more manual/explicit control over the OAuth2 steps. It generates a state token.
A minor detail on that method:
Note that when you construct URLs, the state token should passed as a URL parameter on the .../authorize URL, not embedded as a URL parameter within the .../usercallback URL.
For example:
You would want to redirect the user to:
https://id.shoeboxed.com/oauth/authorize?client_id=<your client id>&response_type=code&scope=all&redirect_uri=<your site>&state=<CSRF token>
where redirect_uri is:
https://script.google.com/macros/d/1234567890abcdefghijklmonpqrstuvwxyz/usercallback
When the user clicked authorize, Shoeboxed should redirect them to:
https://script.google.com/macros/d/1234567890abcdefghijklmonpqrstuvwxyz/usercallback?state=<CSRF token>

oauth2 support for the shoeboxd API has just been added to the cEzyOauth2 Google Apps Script library.
You can copy the pattern to your app and include the library as described here
It uses the statetoken as described by Steve Lieberman, and takes care of the oauth2 conversation, token handling and refreshing automatically.

Related

How can I change the 'typ' of a token provided by Azure AD?

I have a project setup like this:
React frontend
-> authenticates against...
Identity Server
-> which redirects to...
A Microsoft login
I'm using a Clients Credential Provider and it works great - the IS4 redirects to MS login, and then gets redirected with the access token back, which is then passed on to the React app.
Now, I've been tasked with creating a feature to change the user's password. I'm trying to do this by sending the old+new password to IS4, and then calling the MSGraphClient, but I couldn't make it work.
I've tried the Username/Password provider, because I have all the info needed, but I need to change stuff on the ActiveDirectory settings to make my app public. But even then, I don't like that solution.
I've also tried with the On-behalf-of provider, this is the code:
var scopes = new[] { "User.Read",
"Directory.AccessAsUser.All" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "~~";
// Value from app registration
var clientId = "~~";
var clientSecret = "~~";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// This is the incoming token to exchange using on-behalf-of flow
var oboToken = HttpContext.Request.Headers.First(h => h.Key == "Authorization").Value.ToString().Replace("Bearer ", "");
var cca = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
// DelegateAuthenticationProvider is a simple auth provider implementation
// that allows you to define an async function to retrieve a token
// Alternatively, you can create a class that implements IAuthenticationProvider
// for more complex scenarios
var authProvider = new DelegateAuthenticationProvider(async (request) => {
// Use Microsoft.Identity.Client to retrieve token
var assertion = new UserAssertion(oboToken);
var result = await cca.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
var graphClient = new GraphServiceClient(authProvider);
And it kinds of work, because the request is made, but the server throws an error:
AADSTS5002727: Invalid JWT header type specified. Allowed types: 'JWT','http://openid.net/specs/jwt/1.0'.
I checked my token on JWT.io, and the typ is at+jwt... Why? Why is MS sending me a type of token that it doesn't support? How can I change it from my side so it's a plain JWT?
Thanks for any advice, and any other possible solution for this.
To resolve the error "AADSTS5002727: Invalid JWT header type specified. Allowed types: JWT,http ://openid.net/specs/jwt/1.0" , please try the below if helpful:
Please check the version of .Net core you are currently using to generate the token. Try using .Net core 2.2 with IS4.
Try setting IdentityServerOptions.AccessTokenJwtType to empty string or JWT on IdentityServerOptions.
In the mentioned code, replace var oboToken variable directly with the value of token.
var oboToken = "JWT_TOKEN_TO_EXCHANGE";
Please note the below point from MsDoc :
Don't attempt to validate or read tokens for any API you don't own,
including the tokens in this example, in your code. Tokens for Microsoft services can use a special format that will not validate as
a JWT, and may also be encrypted for consumer (Microsoft account)
users
If still the error persists, try upgrading clients to a new token validation library that works with the new style tokens.
Please check whether the below links give you any pointer to resolve the issue:
JWT Token always Invalid · Issue #905 · openiddict/openiddict-core · GitHub
IdentityServer .Net Core 3.0 & Owin/Katana Token validation · Issue #3705 · IdentityServer/IdentityServer4 · GitHub

Get user profile details from Google using access token

We have mobile app developed in react native in which we have to implement Google and Facebook login. We have RN libraries using which we will get Facebook and Google user's profile details. But our requirement is like we need to just pass the access token to web api which is developed in asp.net core, and using the access token we have to verify the access token in asp.net core web api and fetch the user's profile details using Facebook or Google Apis.
It is working fine for Facebook api, below is the code for the same
var httpClient = new HttpClient { BaseAddress = new Uri("https://graph.facebook.com/v2.9/") };
var response = await httpClient.GetAsync($"me?access_token={token}&fields=id,name,email,first_name,last_name,age_range,birthday,gender,locale,picture");
Similarly, when we pass access token(id_token) for google, it is not working, and below is code for the same,
var token ="eyJhb.eyJpc....";
var httpClient1 = new HttpClient { BaseAddress = new Uri("https://www.googleapis.com/oauth2/v3/") };
var response1 = await httpClient1.GetAsync($"userinfo?access_token={token}");
Can anyone please assist me, how can we verify the access token and fetch the user's profile details?
Thanks In Advance.
You can verify your "id_token" and get some user profile details at the same time by making GET request to the next endpoint:
"https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123".
var token ="eyJhb.eyJpc....";
var httpClient1 = new HttpClient { BaseAddress = new Uri("https://oauth2.googleapis.com/") };
var response1 = await httpClient1.GetAsync($"tokeninfo?id_token={token}");
as described in google documentation "https://developers.google.com/identity/sign-in/web/backend-auth" (Calling the tokeninfo endpoint) section.
However in case you want to access google api services:
"id_token" is not meant to be used to access google api services, then you will need to have an "access_token" not an "id_token", you can follow the next documentation for that :
"https://developers.google.com/identity/protocols/oauth2"

What is Code Сhallenge in query param in authorization server like IdentityServer (from JS SPA client pov)?

When I do manual redirect, I'm getting an error from IdentityServer
invalid_request, code challenge required
However when I use oidc-client-js library for the same authorization request, I do not get that error. Library somehow sets code challenge under the hood.
Here is me JS code.
Set up:
const config = {
authority: "https://demo.identityserver.io",
client_id: "interactive.confidential",
redirect_uri: "http://localhost:3000/callback",
response_type: "code",
scope:"openid profile email api offline_access",
post_logout_redirect_uri : "http://localhost:3000/post_logout",
};
const url = `https://demo.identityserver.io/connect/authorize?
client_id=${config.client_id}&
redirect_uri=${config.redirect_uri}&
response_type=${config.response_type}&
scope=${config.scope}`;
My manual authorization redirect request that throws:
const onFormSubmit = async (ev: React.FormEvent) => {
ev.preventDefault();
window.location.replace(url); // I simply do replace
}
Code with the library that doesn't throw:
import Oidc from 'oidc-client';
const onFormSubmit = async (ev: React.FormEvent) => {
ev.preventDefault();
const mgr = new Oidc.UserManager(config);
mgr.signinRedirect(); // login redirect here, no errors
}
I want to understand what code challengem is. And how it gets generated. Give me a hint what to read about it.
I ca go on with the library, but I'd prefer not to import third-party libs into my app where possible.
Authorize Endpoint handle multiple grant types, the way you are sending your request, matched to Authorization Code Grant which needs code_challenge parameter during the request.
Try something simpler to make a request like:
GET /connect/authorize?
client_id=client1&
scope=openid email api1&
response_type=id_token token&
redirect_uri=https://myapp/callback&
state=abc&
nonce=xyz
Read Authorize Endpoint for more information.
Heres an example of generating a challenge code:
private string CreateCodeChallenge()
{
_codeVerifier = RandomNumberGenerator.CreateUniqueId();
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);
var challengeBuffer = sha256.HashData(
CryptographicBuffer.CreateFromByteArray(Encoding.UTF8.GetBytes(_codeVerifier)));
byte[] challengeBytes;
CryptographicBuffer.CopyToByteArray(challengeBuffer, out challengeBytes);
return Base64Url.Encode(challengeBytes);
}
Include the code and the method in the request querystring.
You can generate codes for testing here: https://tonyxu-io.github.io/pkce-generator/
That's as far I've gotten with it but I am shown the login screen.
It's a parameter required by the Proof Key for Code Exchange standard.
OAuth 2.0 public clients utilizing the Authorization Code Grant are susceptible to the authorization code interception attack. This specification describes the attack as well as a technique to mitigate against the threat through the use of Proof Key for Code Exchange (PKCE, pronounced "pixy").

Trustpilot Api - Get Private Product Review always returns Unauthorised response status

I hope someone can help me.
I am trying to retrieve a Product Review for a product using the Trustpilot Api's and am having some success but not getting the results I would expect.
The approach I have taken is as follows:
Get an OAUTH2 token - (Returns a successful response)
Retrieve my business units from a config file and for each business unit get the product reviews using the endpoint: https://api.trustpilot.com/v1/private/business-units/{business-unit}/review?token={OAUTH2 token from step 1} - (Returns a successful response)
For each product review I attempt to retrieve the product review detail. For this I have a couple of options.
(i) Each product review has meta-links and so I can get the product review using the corresponding meta-link and tagging the apikey on e.g. https://api.trustpilot.com/v1/reviews/1234567890abcdefg?apikey={apikey} where the apikey is the one provided up when I registered for a developer account - (Returns a successful response)
(ii) Call the endpoint as documented in the developers.trustpilot.api website (https://developers.trustpilot.com/product-reviews-api#get-private-product-review) : https://api.trustpilot.com/v1/private/product-reviews/{reviewId} - (Returns an Unauthorised status code)
For option (ii) above I have tried multiple ways of passing the apikey (according to the documentation, the endpoint requires the apikey as authorisation.
I am using C# as the language for accessing the Trustpilot apis so the following snippets are how I have tried to call the method.
Set the GetProductReview endpoint as follows:
var url = $"https://api.trustpilot.com/v1/private/product-reviews/" + review.Id.ToString();
using (var client = new HttpClient())
{
var uri = new Uri(url, UriKind.Absolute);
client.BaseAddress = uri;
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new system.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("apikey", apiKey);
try
{
var response = client.GetAsync(uri).Result;
.
.
.
In the above code snippet, the apikey is passed in to the method and passed to the endpoint as a RequestHeader value.
Set the endpoint as follows:
var url = $"https://api.trustpilot.com/v1/private/product-reviews/" + review.Id + $"?apikey={apiKey}";
and call the HttpClient as follows:
using (var client = new HttpClient())
{
var uri = new Uri(url, UriKind.Absolute);
client.BaseAddress = uri;
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
try
{
var response = client.GetAsync(uri).Result;
.
.
.
In both cases I receive an HttpStatus 401 - Unauthorized.
The documentation indicates that I should pass the apikey (which I have done in two different ways).
I have also tried calling the endpoint replacing the ?apikey={apiKey} with ?token={token} in case the documentation is incorrect and requires a token.
Additionally, I have also tried passing the token as a RequestHeader value and receieve the same result (Unauthoirised)
I would really like to use the endpoint:
https://api.trustpilot.com/v1/private/product-reviews/{review}
as this returns more information (for example the sku which would allow me to get access back to the product).
Can anyone please tell me where I am going wrong here?
Thanks in advance
The documentation for the /v1/private/product-reviews/{reviewId} endpoint is indeed incorrect, since it actually requires a Business user OAuth Token instead of an API Key.
In this case, you have two options (and the first one you have used before for the /v1/private/business-units/{businessUnitId}/reviews endpoint):
You can pass the access token in the query string: /v1/private/product-reviews/{reviewId}?token={token}. You mentioned you have tried this. Maybe it did not work for you because your token expired before you tried this approach. Can you try again after refreshing the token?
You can also pass the access token as a Bearer authorization header:
var url = $"https://api.trustpilot.com/v1/private/product-reviews/{review.Id.ToString()}";
using (var client = new HttpClient())
{
...
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
...
}
In any case, you caught an error in the documentation that should be fixed soon. As a rule of thumb, all private endpoints (the ones that have /private/ in the path) require a Business user OAuth Token.
EDIT: The documentation for the /v1/private/product-reviews/{reviewId} endpoint has been fixed. Now it shows that a Business user OAuth Token is required.

Dotnetnuke OAUTH/OWIN external Login with facebook

I developed a module for Dotnetnuke(8) with WebAPI 2 Endpoints via the DNN API
This API is consumed by an Android-App.
To access the functions that are populated via the API, the user needs to authenticate.
I have already implemented the JWT (Json Web Token) Authentication with the WebAPI and login with username/password from the App works fine with this method.
Now I also want to allow users to login via their facebook-login and to get their name and email and photo from their facebook profile to authenticate and authorize them via the DNN-Users-Database and allow/disallow them to use the API functions.
I googled around a lot and read a lot of blogposts and articles about external authentication in the last few days. The following are very interesting and already gave me ann good insight how the process may work:
http://bitoftech.net/2014/08/11/asp-net-web-api-2-external-logins-social-logins-facebook-google-angularjs-app/
Registering Web API 2 external logins from multiple API clients with OWIN Identity
https://www.asp.net/web-api/overview/security/external-authentication-services
but I cannot really find out (and it seems i do not really understand) if and how this can be made working with my dnn-API and the JSON-WebToken Auth Method in my project.
If anybody can help to get me in the right direction, your help is highly appreciated.
Thanks in advance and kind regards
Don
EDIT: The DNN-API gives all the JWT-Functionality I just need to define the api paths and functions. e.g:
'
<Route("{controller}/{action}/{p1}")>
<AcceptVerbs("GET")>
<AllowAnonymous>
Public Function userInf(ByVal p1 As String) As HttpResponseMessage
Dim response As New HttpResponseMessage
Dim pID As Integer = DotNetNuke.Entities.Portals.PortalController.Instance.GetCurrentPortalSettings.PortalId
Dim objUserInfo As New DotNetNuke.Entities.Users.UserInfo
objUserInfo = DotNetNuke.Entities.Users.UserController.Instance.GetUserById(pID, CInt(p1))
If Not objUserInfo Is Nothing Then
If objUserInfo.UserID > 0 Then
response = Request.CreateResponse(System.Net.HttpStatusCode.OK, JsonConvert.SerializeObject("Username: " & objUserInfo.Username.ToString))
Else
' Not logged in
response = Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized, "Not found")
End If
Else
' Not logged in
response = Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized, "Not logged in")
End If
response.Headers.Add("Access-Control-Allow-Origin", CORS) ' <- Allow CORs !!!!
' response.Headers.Add("Access-Control-Request-Method", "*")
Return response
End Function
The Api Path for the DNN Web-API is for authentication:
example.com/DesktopModules/JwtAuth/API/mobile/Login
where I pass the username and password in the request-body as a json-object
(Documentation on dnnsoftware[dot]com / docs / administrators / jwt /)
This all works as expected. The thing now is how to make work the facebook login as an external login work together with my JWT-AUTH
Web api doing authentication by itself, yout need to create OAuthAuthorizationServerOptions and configure web api to use methods, there is an example of how web api token based auth works with standart Bearer token.
There ApplicationOAuthProvider its a class which generates token for inhereting from OAuthAuthorizationServerProvider.
To call method from your token generator you need to get to the path /api/token and request will automaticly give you token and user Claims, which you will define in your token generator.
public void ConfigureOAuth(IAppBuilder app)
{
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
var oauthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(24),
Provider = new ApplicationOAuthProvider(),
};
// Token Generation
app.UseOAuthAuthorizationServer(oauthServerOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
}
Hope this help.