sencha touch 2 with youtube api oAuth - extjs

Is there an example of how to integrate Sencha Touch 2 with youtube API OAuth?
Following the google api docs and Sencha Touch 2: How can I add Google+ Login? example , I used javascript based button rendering for google sign on in my application. However I run into cross origin issues with switching the http vs https context
Blocked a frame with origin "https://accounts.google.com" from accessing a frame with origin "http://localhost:1841. Protocols, domains, and ports must match

oAuth for Client Side Web Applications does not support http protocol for authentication requests.
Note: Requests to Google's authorization server must use https instead of http because the server is only accessible over SSL (HTTPs) and refuses HTTP connections.
Followed below approach:
Render a link to redirect the app to google auth page. You need to register your application with google and get access_token, client_id etc.
In Sencha touch view, add a component like below:
{
styleHtmlContent: true,
html : 'Login to youtube'
}
Note: Entire url used in anchor element must be url encoded string.
Redirect URI configured will be typically same view that rendered the login link. Upon redirect, URL will contain access_token hash tag. Grab it from URL and proceed with regular youtube accessing functionality.
launch: function () {
if ( window.location.hash ) {
this.onAuthRedirect();
}
// Destroy the #appLoadingIndicator element
Ext.fly('appLoadingIndicator').destroy();
// Initialize the main view
Ext.Viewport.add(Ext.create('MyApp.view.Main'));
},
onAuthRedirect: function () {
if (window.location.hash) {
var params = window.location.hash.substring(1).split('&');
if (params[0].split('=')[0] == 'access_token') {
var access_token_value = params[0].split('=')[1];
//validate access_token and proceed with youtube access api stuff
}
}
},

Related

Service to service requests on App Engine with IAP

I'm using Google App Engine to host a couple of services (a NextJS SSR service and a backend API built on Express). I've setup my dispatch.yaml file to route /api/* requests to my API service and all other requests get routed to the default (NextJS) service.
dispatch:
- url: '*/api/*'
service: api
The problem: I've also turned on Identity-Aware Proxy for App Engine. When I try to make a GET request from my NextJS service to my API (server-side, via getServerSideProps) it triggers the IAP sign-in page again instead of hitting my API. I've tried out a few ideas to resolve this:
Forwarding all cookies in the API request
Setting the X-Requested-With header as mentioned here
Giving IAP-secured Web App User permissions to my App Engine default service account
But nothing seems to work. I've confirmed that turning off IAP for App Engine allows everything to function as expected. Any requests to the API from the frontend also work as expected. Is there a solution I'm missing or a workaround for this?
You need to perform a service to service call. That's no so simple and you have not really example for that. Anyway I tested (in Go) and it worked.
Firstly, based your development on the Cloud Run Service to Service documentation page.
You will have this piece of code in NodeJS sorry, I'm not a NodeJS developer and far least a NexJS developer, you will have to adapt
// Make sure to `npm install --save request-promise` or add the dependency to your package.json
const request = require('request-promise');
const receivingServiceURL = ...
// Set up metadata server request
// See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenRequestOptions = {
uri: metadataServerTokenURL + receivingServiceURL,
headers: {
'Metadata-Flavor': 'Google'
}
};
// Fetch the token, then provide the token in the request to the receiving service
request(tokenRequestOptions)
.then((token) => {
return request(receivingServiceURL).auth(null, null, true, token)
})
.then((response) => {
res.status(200).send(response);
})
.catch((error) => {
res.status(400).send(error);
});
This example won't work because you need the correct audience. Here, the variable is receivingServiceURL. It's correct for Cloud Run (and Cloud Functions) but not for App Engine behind IAP. You need to use the Client ID of the OAuth2 credential named IAP-App-Engine-app
Ok, hard to understand what I'm talking about. So, go to the console, API & Services -> Creentials. From there, you have a OAuth2 Client ID section. copy the Client ID column of the line IAP-App-Engine-app, like that
Final point, be sure that your App Engine default service account has the authorization to access to IAP. And add it as IAP-secured Web App User. The service account has this format <PROJECT_ID>#appspot.gserviceaccount.com
Not really clear also. So, go to the IAP page (Security -> Identity Aware Proxy), click on the check box in front of App Engine and go the right side of the page, in the permission panel
In the same time, I can explain how to deactivate IAP on a specific service (as proposed by NoCommandLine). Just a remark: deactivate security when you have trouble with it is never a good idea!!
Technically, you can't deactive IAP on a service. But you can grant allUsers as IAP-secured Web App User on a specific service (instead of clicking on the checkbox of App Engine, click on the checkbox of a specific service). And like that, even with IAP you authorized all users to access to your service. it's an activation without checks in fact.

Frontend/Backend separation: Safari not storing cookies from API which is hosted on a separate domain than its Frontend SPA client

I have a setup which - as far as I can tell - is fairly common nowadays: a backend REST API that lives on its own domain, say myapi.com, and a single page frontend application that is served somewhere else, say myapp.com.
The SPA is a client to the API and the API requires users to authenticate before they can do things.
The backend API is using cookies to store session data for some allowed origins among which myapp.com. This is in order to have a safe bus to transmit and store auth data without having to worry about it client-side.
In Chrome, Opera and Firefox, this works just fine: an API call is made to authenticate the user, Cookies are returned and stored in the browser in order to then be pushed together with the next call.
Safari, on the other hand, does receive the cookies but refuses to store them:
I suspect Safari sees the API domain as a 3rd party cookie domain and therefore blocks the cookies from being stored.
Is this the expected behaviour in Safari? If so, what are some best practices to get around it?
Perpetuating a tradition of answering your own question on this one.
TL;DR this is desired behaviour in Safari. The only way to get around it is to bring the user to a webpage hosted on the API's domain (myapi.com in the question) and set a cookie from there - anything really, you can write a small poem in the cookie if you like.
After this is done, the domain will be "whitelisted" and Safari will be nice to you and set your cookies in any subsequent call, even coming from clients on different domains.
This implies you can keep your authentication logic untouched and just introduce a dumb endpoint that would set a "seed" cookie for you. In my Ruby app this looks as follows:
class ServiceController < ActionController::Base
def seed_cookie
cookies[:s] = {value: 42, expires: 1.week, httponly: true} # value can be anything at all
render plain: "Checking your browser"
end
end
Client side, you might want to check if the browser making the request is Safari and defer your login logic after that ugly popup has been opened:
const doLogin = () => {
if(/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
const seedCookie = window.open(`http://myapi.com/seed_cookie`, "s", "width=1, height=1, bottom=0, left=0, toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no")
setTimeout(() => {
seedCookie.close();
// your login logic;
}, 500);
} else {
// your login logic;
}
}
UPDATE: The solution above works fine for logging a user in, i.e. it correctly "whitelists" the API domain for the current browser session.
Unfortunately, though, it appears that a user refreshing the page will make the browser reset to the original state where 3rd party cookies for the API domain are blocked.
I found a good way to handle the case of a window refresh is to detect it in javascript upon page load and redirect the user to an API endpoint that does the same as the one above, just to then redirect the user to the original URL they were navigating to (the page being refreshed):
if(performance.navigation.type == 1 && /^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
window.location.replace(`http://myapi.com/redirect_me`);
}
To complicate things, it turns out Safari won't store cookies if the response's HTTP status is a 30X (redirect). Thereby, a Safari-friendly solution involves setting the cookies and returning a 200 response together with a JS snippet that will handle the redirect within the browser.
In my case, being the backend a Rails app, this is how this endpoint looks like:
def redirect_me
cookies[:s] = {value: 42, expires: 1.week, httponly: true}
render body: "<html><head><script>window.location.replace('#{request.referer}');</script></head></html>", status: 200, content_type: 'text/html'
end
What worked for me (as mentioned by others / in other similar questions too) is to put my Frontend and Backend under same domain, e.g:
frontend.myapp.com
backend.myapp.com
Then both Safari on Mac Monterey and Safari on iOS 15 started to allow set-cookie from backend.myapp.com (with Secure, HttpOnly, SameSite=none) and access them from frontend.myapp.com

ADAL.JS with Mobile App

I'm trying to integrate some Oracle delivered Mobile Application Framework Apps (MAF) mobile apps with Azure AD authentication. I have tried the Java approach, which apparently doesn't work in my case.
So I decided to try using a Javascript login page option using ADAL.JS. Since MAF creates cross-platform compatible code by transpiling to HTML 5/Javascript/Cordova, I reckoned I could make the JS option work without resorting to having multiple SDK specific solutions like ADAL-Android or ADAL-IOS. Since I can wrap it all in an HTML page as I can use the OAUTH implicit flow option that ADAL.JS requires. I have the ADAL.JS part working from my PC using this example with a local Node/Webpack dev server for the redirect URI. (Note, just like that example, I'd prefer to use the strict adal.js option and avoid any angular-js stuff). However, I'm running into an issue when deployed on the Android mobile device. It appears to be due to the reply URI. After being prompted for Azure credentials and supplying those, the following error is produced.
AADSTS50011: Reply address 'file:///data/user/0/com.company.app/storage/assets/FARs/ViewController/public_html/SignOn/login.html' has an invalid scheme.
I found that when deploying to a mobile device the Azure registered app must be set to type "Native" instead of "Web/API" which I have done. And according to an MSFT example (which I cannot include since I don't have enough rep to include more than two links) the redirect URI must be set to "https://login.microsoftonline.com/common/oauth2/nativeclient". But I still get the same error.
UPDATE since #FeiXue Reply
I'm using the original endpoint not 2.0. When I set the redirectURI as such:
redirectURI=https://login.microsoftonline.com/common/oauth2/nativeclient
The browser returns this in the address bar and remains there on a blank screen and does not issue a token. It does this both on the PC browser and mobile browser.
http://login.microsoftonline.com/common/oauth2/nativeclient#id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzUU4wQlpTN3M0bk4tQmRyamJGMFlfTGRNTSIsImtpZCI6ImEzUU4wQlpTN3M0bk4tQmRyamJGMFlfTGRNTSJ9.(shortened for brevity)&state=e1ce94fb-6310-4dec-9e8b-053727ceb9b8&session_state=1beafa4d-af55-415b-85d5-83e8b4035594
However, for the exact same code, on the PC when I set the redirectURI as such it returns an access token:
redirectURI=https://localhost:8443 <-- port to my local node server
I've also tried it with a redirectURI of urn:ietf:wg:oauth:2.0:oob, but that does not work either.
Code
<!DOCTYPE html>
<html>
<head>
<title>Authenticate User with ADAL JS</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.0/js/adal.js"></script>
<script type="text/javascript">
$(document).ready(function() {
"use strict";
var variables = {
azureAD: "mytenant.onmicrosoft.com",
clientId: "cc8ed7e0-56e9-45c9-b01e-xxxxxxxxxx"
}
window.config = {
tenant: variables.azureAD,
clientId: variables.clientId,
postLogoutRedirectUri: window.location.origin,
redirectUri: "https://login.microsoftonline.com/common/oauth2/nativeclient",
endpoints: {
aisApiUri: "cc8ed7e0-56e9-45c9-b01e-xxxxxxxxxx"
}
//cacheLocation: "localStorage"
};
var authContext = new AuthenticationContext(config);
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
if (isCallback && !authContext.getLoginError()) {
window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
}
var user = authContext.getCachedUser();
if (!user) {
authContext.login();
}
authContext.acquireToken(config.endpoints.aisApiUri, function (error, token) {
if (error || !token) {
console.log("ADAL error occurred in acquireToken: " + error);
return;
}
else {
var accessToken = "Authorization:" + " Bearer " + token;
console.log("SUCCESSFULLY FETCHED TOKEN: " + accessToken);
}
});
});
</script>
</head>
<body>
<h1>Test Login</h1>
</body>
</html>
Update
#FeiXue So I guess from what you're saying the id_token IS the access token? I think then the problem is this.
When the redirectURI="https://localhost:8443" it redirects back to my index.html after AAD login and the authContext.acquireToken() works and returns a valid token.
But when the redirectURI="https://login.microsoftonline.com/common/oauth2/nativeclient" it never redirects back from http://login.microsoftonline.com/common/oauth2/nativeclient#id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1Ni......
While it shows the id_token, it never redirects back to my index.html So I can't make a call to authContext.acquireToken() for passing it onto my web API.
From my research on this topic here is the gist on ADAL.JS and Native (Mobile) Device Support
As #fei-xue-msft mentioned, ADAL.JS is not intended for nor does it work with native/mobile devices. ADAL.JS was written with the “original” Azure endpoint in mind, not the v2.0 endpoint that provides more functionality for mobile/native devices (see more below on the two different endpoint options). There is however an experimental ADAL.JS branch you can try (uses the v2.0 endpoint), but it is not not being actively updated anymore so you are on your own. The new MSFT approach is to use the new MSAL library, which is written towards the v2.0 endpoints. However there is no MSAL-for-JS library yet but rumor is there will be one at some point. For more on the two different Azure endpoints (“original” versus “v2.0”) see the links below. The confusion over this was a source of frustration in my troubleshooting so I help this helps some others going down this track.
So if you are looking to get Azure Oauth authentication on mobile devices, first decide which Azure Endpoint you want to use (Supporting links on that below as v2.0 does have some restrictions that the original endpoint does not). You can determine what your specific endpoints for your tenant are by viewing the Metadata Doc links listed below, just substitute your tenant name or ID. You should be able to use either.
To register an application for a specific type of endpoint (original versus v2.0) use the appropriate App Registration Portal link cited below. Then, to decide what your options are for creating an Azure auth solution for native/mobile device, see the code samples for each endpoint version, and make sure the sample is for “native” else it probably won’t work on your mobile device. For example, you will not see an ADAL.JS sample for the original endpoint library options, but you will see one for Cordova (which is why #fei-xue-msft suggested that approach). For the v2.0 endpoint samples you will see the MSAL/Xamarin options, and for an Javascript option you can try something like the Hello.JS Sample.
Original Endpoint
https://login.microsoft.com/{tenant id}/oauth2/authorize
App Registration Portal: https://portal.azure.com
Code Samples: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-code-samples#native-application-to-web-api
Native Auth Scenarios: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-authentication-scenarios#native-application-to-web-api
OpenID Metadata Doc: https://login.microsoft.com/{tenant id}/.well-known/openid-configuration
V2.0 Endpoint
https://login.microsoftonline.com/{tenant id}/oauth2/v2.0/authorize
App Registration Portal: https://apps.dev.microsoft.com
V2.0 Endpoint Compare: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-compare
Code Sample: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-libraries
OpenID Metadata Doc: https://login.microsoft.com/{tenant id}/v2.0/.well-known/openid-configuration
Are you developing with Azure AD V2.0 endpoint?
If not, we are able to config the redirect URIs as we want on the portal for the native app. However as the error message indicates that the file protocol is not a a validate scheme.
In this scenario, we can use the http or https since you were developing with HTML.
And in the Azure AD V2.0 endpoint, we are not able to set the redirect_Uri for the native app at present. We can use urn:ietf:wg:oauth:2.0:oob or https://login.microsoftonline.com/common/oauth2/nativeclient for the redirect_Uri. The first one is used for the native app for the device and the second we can use for the client which host in browser(web-view).
At last, please ensure that the redirect_uri in the request is using the correct one you register for the portal. You can also test the request on the browser to narrow down whether this issue was cause the incorrect redirect_uri in the request. And for the authorization request, you can refer links below:
Authorize access to web applications using OAuth 2.0 and Azure Active Directory
v2.0 Protocols - OAuth 2.0 Authorization Code Flow
Update(there is no href property if open the HTML from disk which cause the popup page is not closed)
AuthenticationContext.prototype._loginPopup = function (urlNavigate) {
var popupWindow = this._openPopup(urlNavigate, "login", this.CONSTANTS.POPUP_WIDTH, this.CONSTANTS.POPUP_HEIGHT);
if (popupWindow == null) {
this.warn('Popup Window is null. This can happen if you are using IE');
this._saveItem(this.CONSTANTS.STORAGE.ERROR, 'Error opening popup');
this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, 'Popup Window is null. This can happen if you are using IE');
this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, 'Popup Window is null. This can happen if you are using IE');
if (this.callback)
this.callback(this._getItem(this.CONSTANTS.STORAGE.LOGIN_ERROR), null, this._getItem(this.CONSTANTS.STORAGE.ERROR));
return;
}
if (this.config.redirectUri.indexOf('#') != -1)
var registeredRedirectUri = this.config.redirectUri.split("#")[0];
else
var registeredRedirectUri = this.config.redirectUri;
var that = this;
var pollTimer = window.setInterval(function () {
if (!popupWindow || popupWindow.closed || popupWindow.closed === undefined) {
that._loginInProgress = false;
window.clearInterval(pollTimer);
}
try {
//there is no href property if open the HTML from disk
if (popupWindow.location.href.indexOf(registeredRedirectUri) != -1) {
if (that.isAngular) {
that._onPopUpHashChanged(popupWindow.location.hash);
}
else {
that.handleWindowCallback(popupWindow.location.hash);
}
window.clearInterval(pollTimer);
that._loginInProgress = false;
that.info("Closing popup window");
popupWindow.close();
}
} catch (e) {
}
}, 20);
};
This issue is caused that when we open the HTML page from device(disk), the parent HTML page(login page) is not able to get the location of the popup page. So the parent page is not able to close that page based on the location of popup page. To workaround this issue, I suggest that you developing with azure-activedirectory-library-for-cordova or host the login page on the back end of web API.

API on subdomain and oauth

I have an api on a subdomain : api.exemple.com written with symfony2 and my main application on exemple.com (SPA - AngularJs).
I would like to allow user to link their facebook account with their local account. I don't know how to proceed in order to authenticate through my app and use third party oauth provider.
Do you have any clue ?
Thank you
On the angular side, start by opening a new window and send your oauth handler a get request:
self.oauthConnect = function(provider)
{
var url = apiPrefix + '/oauth/tokens/'. provider;
oauthWindow = $window.open(url,'_blank', 'height=600, width=600, top=100, left=300, modal=yes');
oauthWindow.focus();
};
Your PHP api site then redirects to the oauth provider site (i.e. facebook). We use a new client window so our SPA keeps running in spite of the redirect. The provider then presents their login screen and redirects with the oauth token information.
Your PHP api site does what it needs to and generates the actual authorization token (hint: use a json web token). The site then returns an html page back to your angular app.
<body>
<script>
window.opener.oauthCallback('<?php echo $oauthToken; ?>');
</script>
</body>
Your angular controller (that opened the window) will then be called with the oauth token.
$window.oauthCallback = function(oauthToken)
{
oauthWindow.close();
oauthWindow = null;
authManager.oauthToken = oauthToken;
self.oauthSubmit();
};
Easy right? Well not really but it works. In my case I turn right around and:
POST /auth/tokens/oauthToken
to get the real application token. That way my oauth service can be used for multiple applications.

Handling server redirect to Facebook login page in AngularJS

I have an AngularJS app, and a WebApi2 with ASP.NET Identity 2.0.
I am trying to log user in using Facebook account.
I am using this answer to do it.
Getting auth providers is easy, I have problem with the next step.
It says, I should make GET request to my WebApi server, using the Url provided before.
So I make my call and get a HTTP 302 with Location header set to facebook's login page.
The browser doesn't redirect, however.
In Developer Tools I see that GET request to this address is made, but then in console there is
XMLHttpRequest cannot load [url here]. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' therefore not allowed access.
I know this is related to CORS, but like it expects CORS enabled on Facebook's side?
From what I understand: after loggin to Facebook, it redirects me back to my WebApi method registering ExternalLogin, and than back to my AngularApp. Is that correct?
Here's code of my login function:
function facebookLogin() {
getExternalLogins().then(function (data) {
var providerData = null;
for (var i = 0; i < data.length; i++){
if(data[i].Name == 'Facebook'){
providerData = data[i];
break;
}
}
if(providerData != null) {
window.location.href = serverBaseUrl + providerData.Url;
}
},
function (error, status) {
});
}
Update:
I've managed to redirect to Facebook's login page, thanks to CBroe's comment.
The next problem is that after logging in, Facebook redirects me to my WebApi.
How can I redirect user back to Angular app? WebApi can be called from various places, not only from example.com but from example1.com or example2.com also.
Maybe it would be a better idea to perform this login client-side using Facebook API, and then notify the server about it?
But how to perform login on WebApi using FB authResponse?
Try the following steps, they solved the issue for me (for a different login provider):
Remove the built-in login page and controller, and the option to use
ASP.Net identity from the web application; see this article.
Add credentials in the $.ajax call (see below) of in $http:
xhrFields: {
withCredentials: true
},
Add to your web.config the following to enable CORS:
<add name="Access-Control-Allow-Origin" value="FACEBOOK_APP_URL" />
<add name="Access-Control-Allow-Credentials" value="true"/>

Resources