I am trying to configure my Angular app to use the OAuth2 library (angular-oauth2-oidc).
In the file auth.service.ts my OAuthService is configured:
this.oauthService.loginUrl = 'https://serverdomain.com/authorization/';
this.oauthService.redirectUri = 'http://localhost:4200/auth';
this.oauthService.clientId = '1111-2222-3333-4444-5555-6666-7777';
this.oauthService.setStorage(localStorage);
this.oauthService.requireHttps = false;
this.oauthService.responseType = 'token';
this.oauthService.tokenEndpoint = 'https://serverdomain.com/token/';
this.oauthService.oidc = false;
this.oauthService.issuer = 'https://serverdomain.com/authorization/';
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
this.oauthService.requestAccessToken = true;
this.oauthService.showDebugInformation = true;
this.oauthService.scope = 'openid profile email';
this.oauthService.tryLogin({
onTokenReceived: context => {
console.log(context);
}
});
obtainAccessToken() {
this.oauthService.initImplicitFlow();
}
isLoggedIn() {
if (this.oauthService.getAccessToken() === null) {
return false;
}
return true;
}
logout() {
this.oauthService.logOut();
location.reload();
}
logAuthData() {
console.log(this.oauthService.hasValidAccessToken());
}
In my home component I added a button to trigger the implicit flow and get an access token.
After initialization of the implicit flow the app redirects to the correct login page of the provider where I log in and get redirected to my redirectUri of my OAuth configuration.
BUT
If I try to get a state, for example I call isLoggedIn method, I get always false. Also, there is a false return at hasValidAccessToken().
Can anybody show how to correctly configure angular 5 and oauth2?
I need also a possibility to store my given access token to use them in my rest methods to get data.
Need to add JWKs token Validator in your configration. And set Jwks as per your Response type
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
Related
I want to get the messages of users by gmail api. For that google authorization is needed. I managed to authorize the user by following code -
let authBtn = document.getElementById('authorize_button');
const CLIENT_ID = 'XXXXXX-XXXXXXXXXXX.apps.googleusercontent.com';
const API_KEY = 'XXXXXX-XXXXXXXXXXXXXXX';
const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest';
const SCOPES = 'https://www.googleapis.com/auth/gmail.readonly';
let tokenClient;
let gapiInited = false;
let gisInited = false;
authBtn.style.visibility = 'hidden';
function gapiLoaded() {
gapi.load('client', intializeGapiClient);
}
async function intializeGapiClient() {
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC],
});
gapiInited = true;
maybeEnableButtons();
}
function gisLoaded() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '',
});
gisInited = true;
maybeEnableButtons();
}
function maybeEnableButtons() {
if (gapiInited && gisInited) {
authBtn.style.visibility = 'visible';
}
}
function handleAuthClick() {
tokenClient.callback = async (resp) => {
if (resp.error !== undefined) throw (resp);
authBtn.innerText = 'Refresh';
await getMessages();
};
if (gapi.client.getToken() === null) {
tokenClient.requestAccessToken({prompt: 'consent'});
} else {
tokenClient.requestAccessToken({prompt: ''});
}
}
In above code gapi.client.getToken() === null is always false. Everytime I refresh the page I have to reauthorize user with prompt: 'consent'.
I also want user to stay signed in until user sign out.
How can I achieve by modifying the above code?
Can Please someone help me?
You are using a system that requires a server-side authentication flow, read about properly handling that here:
https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
The gapi JavaScript is browser code (you obviously know this because the question specifies all sorts of DOM related code), and therefore Authentication is fundamentally not going to be possible entirely in the browser without a server-side flow to handle the callbacks from Google that occur out-of-band from the browser.
The only exception I can find to the rule of having a server-side component is to credential manager API:
https://developers.google.com/identity/gsi/web/guides/display-browsers-native-credential-manager
It seems to significantly simplify things, but from what I can tell supports Chrome only (maybe including chrome-based browsers Edge, Brave, etc. but maybe not Chromium as it seems to be needing Google accounts in the browser itself, e.g. login is not managed by your code for your website but the user using the browser directly before they visit your site)
I hope everyone is safe and healthy given the current situation.
I have a question in regards to a project with google apps script. I have a web app and I have been able to figure out routing with doGet() using links etc.
//global variables
const sheetId = "foo";
const Route = {};
Route.path = function(route, callback){
Route[route] = callback;
}
function doGet(e){
Route.path("newAccountForm",loadNewForm);
Route.path("updateBrandForm", loadUpdateForm);
if(Route[e.parameters.v]) {
return Route[e.parameters.v]();
} else {
return render("home")
}
};
function loadNewForm() {
const sheetActive = SpreadsheetApp.openById(sheetId);
const mySheet = sheetActive.getSheetByName("Sheet1");
const title = "title";
const index = "index";
return render("addNewAccount",{title: title, index: index});
}
function loadUpdateForm () {
const sheetActive = SpreadsheetApp.openById(sheetId);
const mySheet = sheetActive.getSheetByName("Sheet1");
return render("updateBrand");
}
function render(file,argsObject) {
const tmp = HtmlService.createTemplateFromFile(file);
if(argsObject) {
const keys = Object.keys(argsObject);
keys.forEach(function(key){
tmp[key] = argsObject[key];
})
} // END IF
return tmp.evaluate();
}
The links..
Add New Brand
Update Exisiting Brand
Analytics / Reports
Now I am a bit stuck on handling responses and errors. I have tried using doPost() which works to render a new HTML page. My problem is I am unsure how to tell if the request was successful in the doPost. Is there a way to check that? I can get all the parameters through the event object but not a status.
<form id="myForm" onsubmit="handleNewAccountFormSubmit(this);" method="post" action="<?= ScriptApp.getService().getUrl(); ?>">
I have also been trying to handle it with the included .withFailureHandler() but am unsure how to get it to fire or if it is possible to call back a function from my .GS
I have tried also having the onFail() function outside the FormSubmit function.
function handleNewAccountFormSubmit(formObject) {
google.script.run.withFailureHandler(onFail).withSuccessHandler().processNewAccountForm(formObject);
function onFail(error) {
Logger.log(error)
console.log(error)
return google.script.run.onError();
}
}
I basically want to show if the function ran successfully for user experience but am unsure of best practise or how or even if it is possible(I am sure it is!)
I look forward to any ideas, corrections, and if something is unclear I will do my best to provide more info.
Thanks again.
Use success or failure handlers to alert the user:
function handleNewAccountFormSubmit(formObject) {
alert("Please wait..!")
google.script.run
.withFailureHandler(e => {
console.error(e.message);
alert("Unexpected error! Contact support!")
})
.withSuccessHandler(e => alert("Form submitted successfully!"))
.processNewAccountForm(formObject);
}
I was working with a angular project that include controllers and services. problem comes out from the codes following:
qrcodeCtrl.js
angular
.module('Admin',['ngResource'])
.controller('qrcodeCtrl',function($scope,qrcodeservice,statusservice){
$scope.init = function(){
$scope.getQrcodes();
}
$scope.getQrcodes = function(){
qrcodeservice.getQrcodes()
.then(function(res){
$scope.qrcodes = qrcodeservice.qrcodeList;
$scope.loginstatus = [];
var logininfo = [];
for(var i = 0; i < $scope.qrcodes.length; i++){
logininfo[i] = self.setInterval(getloginfo,10000,i);
}
function getloginfo(i){
uuid = $scope.qrcodes[i];
statusservice.getstatus(uuid).then(function (result) {
console.log(uuid+" "+result.code);
uuid = result.uuid;
switch(result.code){
case 201 : $scope.loginstatus[uuid] = 'login ...';break;
case 200 : $scope.loginstatus[uuid] = 'login success';window.clearInterval(logininfo[i]);break;
case 500 : $scope.loginstatus[uuid] = 'login fail';window.clearInterval(logininfo[i]);break;
default : $scope.loginstatus[uuid] = 'waitting';break; //code 408
}
},function (err) {
console.log(err);
})
return;
}
},function(err){
console.log(err);
})
}
$scope.init();
})
statuservice.js
angular
.module('Admin',['ngResource'])
.service('statusservice',function ($http,$q) {
var auth = this;
auth.getstatus = function(uuid){
var defer = $q.defer();
// //debug
// var code = 200;
// var result = {"uuid":uuid,"code":code};
// defer.resolve(result);
// return defer.promise;
// //=================
$http.get("/check?uuid="+uuid)
.success(function(code){
res = {"uuid":uuid,"code":code};
defer.resolve(res);
})
.error(function(err,status){
console.log(err);
defer.reject(err);
})
return defer.promise;
}
})
about the codes, the service is for getting login info from API which test work fine. and the controller set a interval for getting login info from service constantly. When it turn out login success(200) or login fail (500), the interval will stop. those codes work for login section of a project.
When service doesn't get the login code from API and just write the code, (comment section)the service work fine , the code is 200 and the interval stop, alse view render fine.
But when service get the login code from API, the interval doesn't stop. and from the chrome console , i find out that the code is success change to 200, but still loop the interval. and the view doesn't render to login success.
I was learning angular for few days, could someone tell me why that happen??
your module declaration and usage is incorrect
angular.module('Admin',['ngResource']).something should not be done;
this initialize module admin every time.
you should declare in the below format
var app = angular.module('Admin',['ngResource']);
angular
.module('Admin')
.service('statusservice',function ($http,$q) {
});
angular
.module('Admin')
.controller('qrcodeCtrl',function($scope,qrcodeservice,statusservice){
});
I am currently develloping a little app with angularJS;
the users have to go through a login form and then they can click on a few links who display data fetched from a server.
The login form only takes the user's input and it is then "stored" into a factory
app.factory('IdFormHolder', function () {
var that = this;
that.setId = function (data) {
that = data;
};
that.getId = function () {
return that;
};
return that;
});
Which works fine I need to keep it stored because the server requires the login form to be sent each time I do a $http.get as a header.
Each controller takes the login form from the factory and uses it to get the data from the server.
This works fine until someone decides to refresh the page, at which point it seems the factory is "emptied" from its login form, and the web-app then fails to show anything.
Is there a way to store this login info so that it doesn't get erased so easily ?
You can use this code after youve installed sessionStorage:
app.factory('IdFormHolder', ['$sessionStorage', function ($sessionStorage) {
var that = this;
that.setId = function (data) {
$sessionStorage.id = data;
that = data;
};
that.getId = function () {
return $sessionStorage.id;
};
return that;
}]);
Download Link: https://github.com/gsklee/ngStorage
In order to persist data you'd have to use some kind of local DB || LocalStorage || SessionStorage at least. When initializing the Factory you could check and attempt to retrieve from DB/LS that data and hold it as a variable if it does exist.
Something like
app.factory('IdFormHolder', function () {
this.heldId = attemptToGetSavedDataFromSomewhere(); // would be null otherwise
this.setId = (id) => {
this.heldId = id;
};
this.getId = () => this.heldId;
return this;
});
Using angular-local-storage you can access to the browsers local storage:
app.factory('IdFormHolder', function(localStorageService) {
return {
setId: function(data) {
return localStorageService.set('loggedUserData', data);
},
getId: function() {
return localStorageService.get('loggedUserData');
}
};
});
I'm upgrading/rewriting an existing angular app to use angular2. My problem is that I want to open a OAuth flow in a new pop up window and once the OAuth flow is completed use window.postMessage to communicate back to the angular 2 app that the OAuth flow was successful.
Currently what I have is in the angular 2 service is
export class ApiService {
constructor(private _loggedInService: LoggedInService) {
window.addEventListener('message', this.onPostMessage, false);
}
startOAuthFlow() {
var options = 'left=100,top=10,width=400,height=500';
window.open('http://site/connect-auth', , options);
}
onPostMessage(event) {
if(event.data.status === "200") {
// Use an EventEmitter to notify the other components that user logged in
this._loggedInService.Stream.emit(null);
}
}
}
This template that is loaded at the end of the OAuth flow
<html>
<head>
<title>OAuth callback</title>
<script>
var POST_ORIGIN_URI = 'localhost:8000';
var message = {"status": "200", "jwt":"2"};
window.opener.postMessage(message, POST_ORIGIN_URI);
window.close();
</script>
</head>
</html>
Using window.addEventListener like this seems to completely break the angular 2 app, dereferencing this.
So my question is can I use window.addEventListener or should I not use postMessage to communicate back to the angular2 app?
** Complete angular2 noob so any help is appreciated
I have a complete Angular2 OAuth2 skeleton application on Github that you can refer to.
It makes use of an Auth service for OAuth2 Implicit grants that in turn uses a Window service to create the popup window. It then monitors that window for the access token on the URL.
You can access the demo OAuth2 Angular code (with Webpack) here.
Here is the login routine from the Auth service, which will give you an idea of what's going on without having to look at the entire project. I've added a few extra comments in there for you.
public doLogin() {
var loopCount = this.loopCount;
this.windowHandle = this.windows.createWindow(this.oAuthTokenUrl, 'OAuth2 Login');
this.intervalId = setInterval(() => {
if (loopCount-- < 0) { // if we get below 0, it's a timeout and we close the window
clearInterval(this.intervalId);
this.emitAuthStatus(false);
this.windowHandle.close();
} else { // otherwise we check the URL of the window
var href:string;
try {
href = this.windowHandle.location.href;
} catch (e) {
//console.log('Error:', e);
}
if (href != null) { // if the URL is not null
var re = /access_token=(.*)/;
var found = href.match(re);
if (found) { // and if the URL has an access token then process the URL for access token and expiration time
console.log("Callback URL:", href);
clearInterval(this.intervalId);
var parsed = this.parse(href.substr(this.oAuthCallbackUrl.length + 1));
var expiresSeconds = Number(parsed.expires_in) || 1800;
this.token = parsed.access_token;
if (this.token) {
this.authenticated = true;
}
this.startExpiresTimer(expiresSeconds);
this.expires = new Date();
this.expires = this.expires.setSeconds(this.expires.getSeconds() + expiresSeconds);
this.windowHandle.close();
this.emitAuthStatus(true);
this.fetchUserInfo();
}
}
}
}, this.intervalLength);
}
Feel free to ask if you have any questions or problems getting the app up and running.
So with a bit of investigation found out the problem. I was de-referencing this. This github wiki helped me understand it a bit more.
To solve it for my case needed to do a couple of things. Firstly I created a service that encapsulated the adding of an eventListener
import {BrowserDomAdapter} from 'angular2/platform/browser';
export class PostMessageService {
dom = new BrowserDomAdapter();
addPostMessageListener(fn: EventListener): void {
this.dom.getGlobalEventTarget('window').addEventListener('message', fn,false)
}
}
Then using this addPostMessageListener I can attach a function in my other service to fire
constructor(public _postMessageService: PostMessageService,
public _router: Router) {
// Set up a Post Message Listener
this._postMessageService.addPostMessageListener((event) =>
this.onPostMessage(event)); // This is the important as it means I keep the reference to this
}
Then it works how I expected keeping the reference to this
I think this is the Angular2 way:
(Dart code but TS should be quite similar)
#Injectable()
class SomeService {
DomAdapter dom;
SomeService(this.dom) {
dom.getGlobalEventTarget('window').addEventListener("message", fn, false);
}
}
I fiddled around with this for ages but in the end, the most robust way for me was to redirect the user to the oath page
window.location.href = '/auth/logintwitter';
do the oath dance in the backend (I used express) and then redirect back to a receiving front end page...
res.redirect(`/#/account/twitterReturn?userName=${userName}&token=${token}`);
There are some idiosyncracies to my solution because e.g. I wanted to use only JsonWebToken on the client regardless of login type, but if you are interested, whole solution is here.
https://github.com/JavascriptMick/learntree.org