We are using the generic OpenID Connect middleware to use Google as an external identity provider using IdentityServer3. We don't have MetadataAddress or any special TokenValidationParameters set up (so it should be getting the metadata based on Authority, and then filling in parameters based on that, which should be fine). We are getting the following error highly intermittently. Other questions I've come up with that have this error seem to involve incorrect custom validation and are not intermittent.
Authentication Failed : Microsoft.IdentityModel.Protocols.OpenIdConnectMessage : System.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException : IDX10500 : Signature validation failed.Unable to resolve SecurityKeyIdentifier : 'SecurityKeyIdentifier
(
IsReadOnly = False,
Count = 1,
Clause[0] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause
)
',
token : '{"alg":"RS256","kid":"74e0db263dbc69ac75d8bf0853a15d05e04be1a2"}.{"iss":"https://accounts.google.com","iat":1484922455,"exp":1484926055, <snip more claims>}'.
at System.IdentityModel.Tokens.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)in c : \ workspace \ WilsonForDotNet45Release \ src \ System.IdentityModel.Tokens.Jwt \ JwtSecurityTokenHandler.cs : line 943
at System.IdentityModel.Tokens.JwtSecurityTokenHandler.ValidateToken(String securityToken, TokenValidationParameters validationParameters, SecurityToken & validatedToken)in c : \ workspace \ WilsonForDotNet45Release \ src \ System.IdentityModel.Tokens.Jwt \ JwtSecurityTokenHandler.cs : line 671
at Microsoft.IdentityModel.Extensions.SecurityTokenHandlerCollectionExtensions.ValidateToken(SecurityTokenHandlerCollection tokenHandlers, String securityToken, TokenValidationParameters validationParameters, SecurityToken & validatedToken)in c : \ workspace \ WilsonForDotNet45Release \ src \ Microsoft.IdentityModel.Protocol.Extensions \ SecurityTokenHandlerCollectionExtensions.cs : line 71
at Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationHandler. < AuthenticateCoreAsync > d__1a.MoveNext()
The kid referred to is presently the 2nd of 3 keys at https://www.googleapis.com/oauth2/v3/certs.
Our Options look like this:
var options = new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "Google",
Caption = "Sign in with Google",
Scope = "openid email profile",
ClientId = clientId,
Authority = "https://accounts.google.com/",
AuthenticationMode = AuthenticationMode.Passive,
RedirectUri = new Uri(baseUri, "identity/signin-google").ToString(),
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = context => HandleException(context),
RedirectToIdentityProvider = context => AddLoginHint(context),
},
SignInAsAuthenticationType = signInAsType
};
app.UseOpenIdConnectAuthentication(options);
Is this a configuration issue or some sort of transient error that needs to be dealt with (and if so how)? The end client is doing one retry (though I don't think it's waiting at all) but that doesn't seem to help.
The problem here seems to have been caused by the fact that the default ConfigurationManager caches results for 5 days, while Google rolls over their keys much more frequently (more like daily). With the default behavior of the OWIN middleware, the first request with an unrecognized key will fail, and then on the next request it will refresh the keys.
The solution is to pass in your own ConfigurationManager with a faster AutomaticRefreshInterval. Most of the settings below are as in OpenIdConnectAuthenticationMiddleware
private static void ConfigureIdentityProviders(IAppBuilder app, string signInAsType)
{
var baseUri = new Uri("https://localhost:44333");
var googleAuthority = "https://accounts.google.com/";
var metadataAddress = googleAuthority + ".well-known/openid-configuration";
var httpClient = new HttpClient(new WebRequestHandler());
httpClient.Timeout = TimeSpan.FromMinutes(1);
httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
var configMgr = new ConfigurationManager<OpenIdConnectConfiguration>(metadataAddress, httpClient)
{
// Default is 5 days, while Google is updating keys daily
AutomaticRefreshInterval
=
TimeSpan
.FromHours(12)
};
var options = new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "Google",
Caption = "Google",
Scope = "openid email profile",
ClientId = GoogleClientId,
Authority = googleAuthority,
ConfigurationManager = configMgr,
AuthenticationMode = AuthenticationMode.Passive,
RedirectUri =
new Uri(baseUri, "identity/signin-google").ToString(),
SignInAsAuthenticationType = signInAsType
};
app.UseOpenIdConnectAuthentication(options);
}
Related
I am trying to add an authentication provider to a Pulumi WebApp but is totaly unclear to me how to achieve that. The class WebApp from package #pulumi/azure-native/web only offers the property identity but no property tho assign e.g. a Microsoft AD. Can anybody provide a hint on how to set this up?
There are some of the Pulumi Azure pre-requisites and have the appropriate permissions in your tenant and Azure subscription.
Follow the below steps to add the authentication to app service webapp with pulumi and deploy:
Creating the project:
Start by creating the application, and adding the AzureAD package we’ll need to create the Azure AD application registration.
pulumi new azure-csharp `
--name easyauth-webapp `
--description "azure ad secured app" `
--stack dev `
--config azure-native:location=eastus
dotnet add package Pulumi.AzureAD
We next need to update the contents of the pulumi.dev.yaml file to contain a few additional config items. Paste the following into the file:
config:
azure-native:location: eastus
azure-native:subscriptionId: UPDATE_ME
azure-native:tenantId: UPDATE_ME
easyauth-webapp:tenantId: UPDATE_ME
easyauth-webapp:ownerId: UPDATE_ME
easyauth-webapp:siteName: UPDATE_ME
easyauth-webapp:appRegistrationName: UPDATE_ME
You can set siteName and appRegistrationName to whatever you want.
The subscriptionId and tenantId should be set to the appropriate target’s for your Azure app service and Azure AD application registration, respectively.
The following commands may be helpful in retrieving these values:
# Get your user's id
az ad signed-in-user show --query objectId
# List all subscriptions (and their tenant) that you have access to
az account list
Deploy the website (with no security):
We’ll next create the website we want to deploy. We’re going to use the run from ZIP package functionality to deploy the contents of the wwwroot folder.
Create that folder and add some content to the index.htm file:
Ex:
<!-- wwwroot/index.htm -->
<html>
<head>
<title>A very secure app</title>
</head>
<body>
Hello EasyAuth with Pulumi!
</body>
</html>
Now we can deploy this file to Azure with Pulumi.
Modify the MyStack.cs file to contain the below code, which has been adapted from the Pulumi Function Stack example:
// MyStack.cs
using System;
using Pulumi;
using Pulumi.AzureAD;
using Pulumi.AzureAD.Inputs;
using Pulumi.AzureNative.Resources;
using Pulumi.AzureNative.Storage;
using Pulumi.AzureNative.Storage.Inputs;
using Pulumi.AzureNative.Web;
using Pulumi.AzureNative.Web.Inputs;
class MyStack : Stack
{
public MyStack()
{
var config = new Pulumi.Config();
var tenantId = config.Require("tenantId");
var ownerId = config.Require("ownerId");
var siteName = config.Require("siteName");
var appRegistrationName = config.Require("appRegistrationName");
var rg = new ResourceGroup($"RG-{siteName}");
var storageAccount = new StorageAccount("storageaccount", new StorageAccountArgs
{
ResourceGroupName = rg.Name,
Kind = "StorageV2",
Sku = new SkuArgs
{
Name = SkuName.Standard_LRS,
},
});
var appServicePlan = new AppServicePlan("appserviceplan", new AppServicePlanArgs
{
ResourceGroupName = rg.Name,
Kind = "App",
Sku = new SkuDescriptionArgs
{
Tier = "Basic",
Name = "B1",
},
});
var container = new BlobContainer("zips", new BlobContainerArgs
{
AccountName = storageAccount.Name,
PublicAccess = PublicAccess.None,
ResourceGroupName = rg.Name,
});
var blob = new Blob("appservice-blob", new BlobArgs
{
ResourceGroupName = rg.Name,
AccountName = storageAccount.Name,
ContainerName = container.Name,
Type = BlobType.Block,
Source = new FileArchive("wwwroot"),
});
var codeBlobUrl = SignedBlobReadUrl(blob, container, storageAccount, rg);
var app = new WebApp("app", new WebAppArgs
{
Name = siteName,
ResourceGroupName = rg.Name,
ServerFarmId = appServicePlan.Id,
SiteConfig = new SiteConfigArgs
{
AppSettings = {
new NameValuePairArgs{
Name = "WEBSITE_RUN_FROM_PACKAGE",
Value = codeBlobUrl,
}
},
}
});
this.Endpoint = app.DefaultHostName;
}
// From https://github.com/pulumi/examples/blob/master/azure-cs-functions/FunctionsStack.cs
private static Output<string> SignedBlobReadUrl(Blob blob, BlobContainer container, StorageAccount account, ResourceGroup resourceGroup)
{
return Output.Tuple<string, string, string, string>(
blob.Name, container.Name, account.Name, resourceGroup.Name).Apply(t =>
{
(string blobName, string containerName, string accountName, string resourceGroupName) = t;
var blobSAS = ListStorageAccountServiceSAS.InvokeAsync(new ListStorageAccountServiceSASArgs
{
AccountName = accountName,
Protocols = HttpProtocol.Https,
SharedAccessStartTime = "2021-01-01",
SharedAccessExpiryTime = "2030-01-01",
Resource = SignedResource.C,
ResourceGroupName = resourceGroupName,
Permissions = Permissions.R,
CanonicalizedResource = "/blob/" + accountName + "/" + containerName,
ContentType = "application/json",
CacheControl = "max-age=5",
ContentDisposition = "inline",
ContentEncoding = "deflate",
});
return Output.Format($"https://{accountName}.blob.core.windows.net/{containerName}/{blobName}?{blobSAS.Result.ServiceSasToken}");
});
}
[Output] public Output<string> Endpoint { get; set; }
}
We can now deploy the site and verify it has worked as intended:
pulumi up --stack dev
curl (pulumi stack --stack dev output Endpoint)
[
Securing the site:
To configure Easy Auth we first create an Azure AD application registration.
In this example I’m specifying AzureADMyOrg which restricts access to the tenant the application registration is deployed in. I’m also adding a RedirectUri that points at the Easy Auth middleware of the deployed site. A password is needed to use as a client secret (the web application being the client in this case).
Once the application registration is created we can add WebAppAuthSettings to our site. The example specifies no anonymous access (using RedirectToLoginPage), and connects the site to the application registration using the ClientId and ClientSecret (password).
Paste the below code just after the this.Endpoint... code in the above MyStack.cs:
// MyStack.cs
// After this.Endpoint = app.DefaultHostName;
var adApp = new Application("ADAppRegistration", new ApplicationArgs
{
DisplayName = appRegistrationName,
SignInAudience = "AzureADMyOrg",
Owners = new[] { ownerId },
Web = new ApplicationWebArgs
{
ImplicitGrant = new ApplicationWebImplicitGrantArgs
{
IdTokenIssuanceEnabled = true
},
RedirectUris = new System.Collections.Generic.List<string> { $"https://{siteName}.azurewebsites.net/.auth/login/aad/callback" }
}
}
);
var applicationPassword = new ApplicationPassword("appPassword", new ApplicationPasswordArgs
{
ApplicationObjectId = adApp.Id,
DisplayName = "Client secret for web app"
});
var allowedAudience = adApp.ApplicationId.Apply(id => $"api://{id}");
var authSettings = new WebAppAuthSettings("authSettings", new WebAppAuthSettingsArgs
{
ResourceGroupName = rg.Name,
Name = app.Name,
Enabled = true,
UnauthenticatedClientAction = UnauthenticatedClientAction.RedirectToLoginPage,
DefaultProvider = BuiltInAuthenticationProvider.AzureActiveDirectory,
ClientId = adApp.ApplicationId,
ClientSecret = applicationPassword.Value,
Issuer = $"https://sts.windows.net/{tenantId}/v2.0",
AllowedAudiences = new[] { allowedAudience },
});
We can now update the site, From the command line we can’t get much further than this.
But in a browser we’ll get redirected to complete the login flow and access the site.
pulumi up --stack dev
# Redirect to HTTPS
curl (pulumi stack --stack dev output Endpoint)
# Access denied
curl "https://$(pulumi stack --stack dev output Endpoint)"
Refer this Github link for pulumi samples.
I am trying to implement a signature verification endpoint - or ASP.net WebAPI action filter, to verify that a token has in fact come from AWS Cognito - validate its signature.
I am using the following code, but it always returns invalid. The Javascript code example also below works perfectly with the same keys / token.
Can anyone help?
Thanks,
KH
CSharp
public IHttpActionResult Verify([FromBody] string accessToken)
{
string[] parts = accessToken.Split('.');
//From the Cognito JWK set
//{"alg":"RS256","e":"myE","kid":"myKid","kty":"RSA","n":"myN","use":"sig"}]}
var n = Base64UrlDecode("q7ocE2u-JSe1P4AF6_Nasae7e7wUoUxJq058CueDFs9R5fvWQTtAN1rMxBCeLQ7Q8Q0u-vqxr83b6N9ZR5zWUU2stgYzrDTANbIn9zMGDZvSR1tMpun5eAArKW5fcxGFj6klQ0bctlUATSGU5y6xmYoe_U9ycLlPxh5mDluR7V6GbunE1IXJHqcyy-s7dxYdGynTbsLemwmyjDaInGGsM3gMdPAJc29PXozm87ZKY52U7XQN0TMB9Ipwsix443zbE_8WX2mvKjU5yvucFdc4WZdoXN9SGs3HGAeL6Asjc0S6DCruuNiKYj4-MkKh_hlTkH7Rj2CeoV7H3GNS0IOqnQ");
var e = Base64UrlDecode("AQAB");
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.ImportParameters(new RSAParameters
{
Exponent = new BigInteger(e).ToByteArrayUnsigned(),
Modulus = new BigInteger(n).ToByteArrayUnsigned()
});
SHA512Managed sha512 = new SHA512Managed();
byte[] hash = sha512.ComputeHash(Encoding.UTF8.GetBytes(parts[0] + "." + parts[1]));
RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(provider);
rsaDeformatter.SetHashAlgorithm(sha512.GetType().FullName);
if (!rsaDeformatter.VerifySignature(hash, Base64UrlDecode(parts[2])))
throw new ApplicationException(string.Format("Invalid signature"));
return Ok(true);
}
// from JWT spec
private static byte[] Base64UrlDecode(string input)
{
var output = input;
output = output.Replace('-', '+'); // 62nd char of encoding
output = output.Replace('_', '/'); // 63rd char of encoding
switch (output.Length % 4) // Pad with trailing '='s
{
case 0: break; // No pad chars in this case
case 1: output += "==="; break; // Three pad chars
case 2: output += "=="; break; // Two pad chars
case 3: output += "="; break; // One pad char
default: throw new System.Exception("Illegal base64url string!");
}
var converted = Convert.FromBase64String(output); // Standard base64 decoder
return converted;
}
JavaScript
var jwkToPem = require('jwk-to-pem');
var jwt = require('jsonwebtoken');
var jwks = //jwk set file, which you can find at https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json.
//Decode token
var decoded = jwt.decode(token, {complete: true});
//Get the correct key from the jwks based on the kid
var jwk = jwks.keys.filter(function(v) {
return v.kid === decoded.header.kid;
})[0];
//Convert the key to pem
var pem = jwkToPem(jwk);
//Verify the token with the pem
jwt.verify(token, pem, function(err, decoded) {
//if decoded exists, its valid
});
Replace
SHA512Managed sha512 = new SHA512Managed();
by
SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider();
Don't forget to set properly the hash algorithm properly as well
rsaDeformatter.SetHashAlgorithm("SHA256");
Flo's answer works but its built into .net now. Using https://rafpe.ninja/2017/07/30/net-core-jwt-authentication-using-aws-cognito-user-pool/ which has more details on how to build it into the .net core middleware:
public RsaSecurityKey SigningKey(string Key, string Expo)
{
return new RsaSecurityKey(new RSAParameters()
{
Modulus = Base64UrlEncoder.DecodeBytes(Key),
Exponent = Base64UrlEncoder.DecodeBytes(Expo)
});
}
public TokenValidationParameters TokenValidationParameters()
{
// Basic settings - signing key to validate with, audience and issuer.
return new TokenValidationParameters
{
// Basic settings - signing key to validate with, IssuerSigningKey and issuer.
IssuerSigningKey = this.SigningKey(CognitoConstants.key,CognitoConstants.expo),
ValidIssuer = CognitoConstants.Issuer,
ValidAudience = CognitoConstants.clientid,//Same value you send in the cognito request url
// when receiving a token, check that the signing key
ValidateIssuerSigningKey = true,
// When receiving a token, check that we've signed it.
ValidateIssuer = true,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// Do not validate Audience on the "access" token since Cognito does not supply it but it is on the "id"
ValidateAudience = true,
// This defines the maximum allowable clock skew - i.e. provides a tolerance on the token expiry time
// when validating the lifetime. As we're creating the tokens locally and validating them on the same
// machines which should have synchronised time, this can be set to zero. Where external tokens are
// used, some leeway here could be useful.
ClockSkew = TimeSpan.FromMinutes(0)
};
}
private bool ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
if (tokenHandler.CanReadToken(token))
{
var validationParams = TokenValidationParameters();
SecurityToken validatedToken;
//ValidateToken throws if it fails so if you want to return false this needs changing
var principal = tokenHandler.ValidateToken(token, validationParams, out validatedToken);
return validatedToken != null;
}
return false;
}
I want a sample application in c#.net which can create users in Office 365 using Microsoft API .
I wish to do it in code, not using Powershell.
You can use the Microsoft Graph API - Create User:
Register a Native Client App on Azure AD, assign the "Microsoft Graph" > "Read and Write Directory Data" permission.
string authority = "https://login.windows.net/yourdomain.onmicrosoft.com";
string clientId = "{app_client_id}";
Uri redirectUri = new Uri("http://localhost");
string resourceUrl = "https://graph.microsoft.com";
HttpClient client = new HttpClient();
AuthenticationContext authenticationContext = new AuthenticationContext(authority, false);
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(resourceUrl,
clientId, redirectUri, PromptBehavior.Always);
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + authenticationResult.AccessToken);
string content = #"{
'accountEnabled': true,
'displayName': 'testuser',
'mailNickname': 'test',
'passwordProfile': {
'forceChangePasswordNextSignIn': true,
'password': 'pass#wd12345'
},
'userPrincipalName': 'testuser#yourdomain.onmicrosoft.com'
}";
var httpContent = new StringContent(content, Encoding.GetEncoding("utf-8"), "application/json");
var response = client.PostAsync("https://graph.microsoft.com/v1.0/users", httpContent).Result;
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
We need to update users claims after they log in to our website. This is caused by changes in the users licenses done by another part of our system.
However I am not able to comprehend how to update the claims without logout/login.
Rigth now this is our client setup
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
//user validation host
Authority = UrlConstants.BaseAddress,
//Client that the user is validating against
ClientId = guid,//if not convertet to Gui the compare from the server fails
RedirectUri = UrlConstants.RedirectUrl,
PostLogoutRedirectUri = UrlConstants.RedirectUrl,
ResponseType = "code id_token token",
Scope = "openid profile email roles licens umbraco_api umbracoaccess",
UseTokenLifetime = false,
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
_logger.Info("ConfigureAuth", "Token valdidated");
var id = n.AuthenticationTicket.Identity;
var nid = new ClaimsIdentity(
id.AuthenticationType,
Constants.ClaimTypes.GivenName,
Constants.ClaimTypes.Role);
// get userinfo data
var uri = new Uri(n.Options.Authority + "/connect/userinfo");
var userInfoClient = new UserInfoClient(uri,n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
userInfo.Claims.ToList().ForEach(ui => nid.AddClaim(new Claim(ui.Item1, ui.Item2)));
var licens = id.FindAll(LicenseScope.Licens);
nid.AddClaims(licens);
// keep the id_token for logout
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
_logger.Info("ConfigureAuth", "AuthenticationTicket created");
},
RedirectToIdentityProvider = async n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
_logger.Debug("ConfigureAuth", "id_token for logout set on request");
_logger.Debug("ConfigureAuth", "Old PostLogoutRedirectUri: {0}", n.ProtocolMessage.PostLogoutRedirectUri.ToString());
n.ProtocolMessage.IdTokenHint = idTokenHint;
var urlReferrer = HttpContext.Current.Request.UrlReferrer.ToString();
if (!urlReferrer.Contains("localhost"))
{
n.ProtocolMessage.PostLogoutRedirectUri = GetRedirectUrl();
}
else
{
n.ProtocolMessage.PostLogoutRedirectUri = urlReferrer;
}
_logger.Debug("ConfigureAuth", string.Format("Setting PostLogoutRedirectUri to: {0}", n.ProtocolMessage.PostLogoutRedirectUri.ToString()));
}
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.AuthenticationRequest)
{
n.ProtocolMessage.RedirectUri = GetRedirectUrl2();
n.ProtocolMessage.AcrValues = GetCurrentUmbracoId();
_logger.Debug("ConfigureAuth", string.Format("Setting RedirectUri to: {0}", n.ProtocolMessage.RedirectUri.ToString()));
}
},
}
});
We get our custom claims in SecurityTokenValidated
var licens = id.FindAll(LicenseScope.Licens);
nid.AddClaims(licens);
I do not follow how to get this without doing a login? Any help is highly appreciated.
That's a reminder that you should not put claims into tokens that might change during the lifetime of the session.
That said - you can set a new cookie at any point in time.
Reach into the OWIN authentication manager and call the SignIn method. Pass the claims identity that you want to serialize into the cookie.
e.g.
Request.GetOwinContext().Authentication.SignIn(newIdentity);
I am trying to connect with my shopify shop through the google javascrip. The schema for authentication should be something similar to the one you can find on google documentation for twitter. I'am trying the following code, but I always get the error:{"errors":"[API] Invalid API key or access token (unrecognized login or wrong password)"}
function getInfofromshopify() {
var handle = "01-02-0316_cmt_utensili"
var urljson ="https://mysitename.myshopify.com/admin/products.json?handle="+handle;
var oAuthConfig = UrlFetchApp.addOAuthService("shopify");
oAuthConfig.setAccessTokenUrl("https://mysitename.myshopify.com/admin/oauth/access_token");
oAuthConfig.setRequestTokenUrl("https://mysitename.myshopify.com/admin/oauth/access_token");
oAuthConfig.setAuthorizationUrl("https://mysitename.myshopify.com/admin/oauth/authorize");
oAuthConfig.setConsumerKey(API_KEY);
oAuthConfig.setConsumerSecret(Shared_secret);
var options =
{
"oAuthServiceName" : "shopify",
"oAuthUseToken" : "always"
};
var response = UrlFetchApp.fetch(urljson,options);
var responsestr = response.getContentText();
var result = Utilities.jsonParse(responsestr)
}
This worked for me:
var url = "https://<YOUR_SHOP>.myshopify.com/admin/products.json";
var username = "<YOUR_SHOPIFY_API_KEY>";
var password = "<YOUR_SHOPIFY_API_PASSWORD>";
var response = UrlFetchApp.fetch(url, {"method":"get", "headers": {"Authorization": "Basic " + Utilities.base64Encode(username + ":" + password)}});