Google SignIn: Redirect Uri in GSI client flow - reactjs

I’m trying to use the new Google Identity Services to sign in an user to get access to manage his calendars.
My current auth function looks like this in JS:
const auth = async () => {
return new Promise((resolve) => {
const GTokenClient = google.accounts.oauth2.initTokenClient({
client_id: GOOGLE_CLIENT_ID,
scope: GOOGLE_CALENDAR_SCOPE,
prompt: '',
callback: resolve
});
if (gapi.client.getToken() === null) {
GTokenClient.requestAccessToken({ prompt: 'consent' });
} else {
GTokenClient.requestAccessToken({
prompt: ''
});
}
});
};
In desktop web browsers it works fine and the promise resolves but in smartphones (currently trying with an iPhone12) the browser opens a new tab and it stays there loading after giving permissions.
I’m aware that you can set a redirect with the code flow, but it is possible to do the same with the client flow?
I don’t know what to do honestly because there are no examples to implement this behavior using the client flow in the Google documentation.
I only want to be able to redirect the user to the initial screen, if I close the tab that google creates for signing in the calendar is loaded and everything seems fine, it is just a matter of redirection.

Related

How do I correctly sign out of my Azure MVC app that uses oidc?

If I manually go to this url: https://localhost:1234/signout-oidc
Then it signs out of my azure AD connected mvc application.
However going to this url just presents me with a blank white screen. I'm trying to do this on a 'log out' button on my MVC site, so ideally I would have it redirect. I might also want to do some custom logic so it would be good if I could put this into an action.
I've seen some suggestions like this:
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" });
However nothing happens when running these lines, I remain signed in.
Can anyone tell me what the correct way is to sign out the way that URL does within my application?
In the controller, simply put:
return SignOut("Cookies", "OpenIdConnect");
To control the URL, in the program.cs or startup.cs, put a handler in:
builder.Services.AddAuthentication(authOptions =>
{
authOptions.DefaultScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddMicrosoftIdentityWebApp(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Events.OnSignedOutCallbackRedirect += context =>
{
context.Response.Redirect("/"); // Redirect URL
context.HandleResponse();
return System.Threading.Tasks.Task.CompletedTask;
};
});

Reactjs AWS Cognito - How to Handle newPasswordRequired

I'm using AWS Cognito Javascript SDK in a react application. I have a user that was created in the AWS Console by an admin. The user recieves an email with their username and temporary password. Now based on my understanding, I have to go through the newPasswordRequired flow, but I have been struggling with this for several hours now trying multiple different approaches and none are getting me anywhere. When I check the AWS Console, the user in the user pool is set to FORCE_CHANGE_PASSWORD.
Here is my code in its current state. Please if someone can help me solve the process as I am fairly new to using Cognito authentication.
function setNewPassword(data) {
console.log("data \n", data)
var authenticationData = {
Username: data.username,
Password: data.temp_password
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
authenticationData
);
var userPool = new AmazonCognitoIdentity.CognitoUserPool(config.cognito);
var userData = {
Username: data.username,
Pool: userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
return new Promise(function(resolve, reject) {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function(result) {
resolve(resolve);
},
onFailure: function(err) {
reject(err);
},
newPasswordRequired: function(userAttributes, requiredAttributes) {
this.cognitoUser.completeNewPasswordChallenge(newPassword, attributesData, this)
}
});
});
}
In the browser console, I am getting the following error:
{code: "UnknownError", message: "Unkown error"}
Have you looked at using the AWS Amplify JS library for help here? There's a suite of React components already built to help with these sorts of things. Here's a link to the relevant documentation. The code is open sourced on GitHub and you might be able to just use the RequireNewPassword component, or at least find inspiration from it.

Angular & TypeScript Promise

I'm sorry about my English but I'm not fluent.
I've a problem and I don't understand why when I use Google Chrome with the other's broswers the web aplication works correctly.
The problem is when I make click in the buttom logout using Google Chrome the web page don't do the logout in the first time I click above the logout. But when I click the second time works correctly and I'm redirected to the logout page.
With other's browsers work at the first time but not with google chrome
Here is the code and thank you very much.
logout() {
let token = this.token.rcs().token;
this.token.removeAllTokens();
let redirect = () => {
//oauthState.config.logoutRedirect && window.location.replace(oauthState.config.logoutRedirect);
window.location.replace(oauthState.config.logoutRedirect);
/* oauthState.config.logoutRedirect send me to the logout page. */
};
let redirect1 = () => {
alert("error tt")
};
this.$http
.post(oauthState.config.baseAuthUrl.concat('/').concat(oauthState.config.revokePath), {token: token.accessToken}, {withCredentials: true})
.then(redirect, redirect1);
}

How should I make sure the user accessing a backend rendered frontend route is authenticated?

I'm using Laravel and Angular to write a web app.
In the front end Laravel is used to create the basic template, but otherwise controlled by Angular. In the back end laravel is used to create a restful API.
I have a few routes like this:
Route::group(['domain' => 'domain.com'], function() {
Route::get('/', ['as' => 'home', function () {
return view('homepage');
}]);
Route::get('/login', ['as' => 'login', function () {
return view('login');
}]);
//users should be authenticated before accessing this page
Route::get('/dashboard', ['as' => 'dashboard', function () {
return view('dashboard');
}]);
});
Route::group(['domain' => 'api.domain.com', 'middleware' => ['oauth']], function() {
Route::post('/post/create', ['uses' => 'PostController#store']);
Route::get('/post/{id}', ['uses' => 'PostController#show']);
//other API endpoints
// ...
});
I want to make sure my domain.com/dashboard URL is only accessed by authenticated users.
In my backend I have OAuth implemented for my API routes which makes sure the user accessing those routes are authentic. Laravel's Auth::once() is used by the OAuth library to make sure the user credentials are correct then generates an access_token. Since Auth::once() is a "stateless" function no session or cookies are utilized and I cannot use Auth::check() to make sure a user is authenticated before the dashboard page is rendered.
How should I go about checking to see if the user trying to access domain.com/dashboard is authenticated? Should I send the access_token in the header when I forward the user from /login to /dashboard? Or should I implement Laravel's a session/cookie based authentication?
EDIT: As per this: Adding http headers to window.location.href in Angular app I cannot forward the user to the dashboard page with an Authorization header.
In order to reuse my API for my mobile apps I STRONGLY prefer to use some sort of token based authentication.
I would advise to use JWT (JSON Web Tokens) to control for authentication.
I think there are several tutorials for their use with Lavarel and AngularJS. I'm more into Python and I use Flask, but the followings look interesting :
Simple AngularJS Authentication with JWT : the AngularJS configuration
Token-Based Authentication for AngularJS and Laravel Apps : the connection with Laravel
JSON Web Token Tutorial: An Example in Laravel and AngularJS
Pierre was right in suggesting JWT for your token based auth.
When the user successfully logs in, before you finish the request, you can create a JWT and pass that back to the client. You can store it on the client (localStorage, sessionStorage) if you want. Then on subsequent requests, put the JWT inside of your Authorization header. You can then check for this header in your middleware and prevent access to your API routes if the token is valid. You can also use that token on the client and prevent Angular from switching routes if the token doesn't exists or isn't valid.
Now if you are trying to prevent the user from accessing the page entirely on initial load (Opens browser, goes straight to domain.com/dashboard), then I believe that is impossible since there is no way to get information about the client without first loading some code on the page.
Not sure about Angular, as I have never used it, but have you tried targeting a controller with your dashboard route? For example
Route::get('/dashboard', [
'uses' => 'UserController#getDashboard',
'as' => 'dashboard',
'middleware' => 'auth'
]);
UserController.php (I'm assuming you have a blade called dashboard.blade.php)
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class UserController extends Controller
{
public function getDashboard()
{
if(Auth::user()) {
return view('dashboard');
} else {
redirect()->back();
}
}
}
Also, you could always group whichever routes you want to protect with this (taken from the Laravel 5.2 documentation):
Route::group(['middleware' => 'auth'], function () {
Route::get('/', function () {
// Uses Auth Middleware
});
Route::get('user/profile', function () {
// Uses Auth Middleware
});
});
EDIT: Regarding the session token, your login blade should have this in its code:
<input type="hidden" name="_token" value="{{ Session::token() }}">
When the /dashboard HTML loads, does it already include data specific to the user's account?
I would suggest to load the /dashboard HTML separately from the user's data, and hide the dashboard items with ng-cloak while Angular loads the data to populate it.
Redirect to /dashboard URL
Load (static?) dashboard HTML / Angular app, hiding all parts with ng-cloak.
Have Angular access the API using the access_token to load all dashboard data.
Revealing the parts of the dashboard when the data from the API comes in, or showing an error message if the access_token wan't valid.
That way, your /dashboard HTML could actually be just a static HTML file and directly served by the web server and cached by any proxy on the way.
If that isn't an option, you could put your access_token into a cookie with Javascript that runs on the /login view, then redirect to /dashboard, then have your server-side /dashboard view read the cookie to check if the access_token is valid. But that sounds messy and mixes up things that should be separated.
#Pierre Cordier #Mr_Antivius Thank you guys for your answer, it helped me get insight into the problem and allowed me to tinker with with JWT but ultimately did not product a solution for me.
To allow only authenticated users to access domain.com/dashboard I had to implement a hybrid session and OAuth authentication system. I decided to go with Sentinel (instead of Laravel's out of the box auth system) because it has a user permission system I need in other places in my app. I use this library for the OAuth Server.
Here is what I do in my controller:
POST domain.com/auth/authenticate:
public function processLogin(Request $request)
{
$credentials = [
'email' => $request->input('username'),
'password' => $request->input('password'),
];
try
{
$sentinel = Sentinel::authenticate($credentials);
}
catch (\Cartalyst\Sentinel\Checkpoints\ThrottlingException $e)
{
$response = ['error' => [$e->getMessage()]];
$httpStatus = 429;
return response()->json($response, $httpStatus);
}
catch (\Cartalyst\Sentinel\Checkpoints\NotActivatedException $e)
{
$response = ['error' => [$e->getMessage()]];
$httpStatus = 401;
return response()->json($response, $httpStatus);
}
if ($sentinel) //user credentials correct
{
//get oauth token
$oauthToken = Authorizer::issueAccessToken();
$response = ['success' => true, 'user' => ['id' => $sentinel->id, 'email' => $sentinel->email]] + $oauthToken;
$httpStatus = 200;
}
else
{
$response = ['success' => false, 'error' => ['Incorrect credentials']];
$httpStatus = 401;
}
return response()->json($response, $httpStatus);
}
Here is the method the OAuth library looks at to authenticate the user:
public function verifyAuth($email, $password)
{
$credentials = [
'email' => $email,
'password' => $password,
];
if ($user = Sentinel::stateless($credentials))
{
return $user->id;
}
else
{
return false;
}
}
This would create a response like so:
{
"success": true,
"user": {
"id": 1,
"email": "email#domain.tld"
},
"access_token": "6a204bd89f3c8348afd5c77c717a097a",
"token_type": "Bearer",
"expires_in": 28800,
"refresh_token": "092a8e1a7025f700af39e38a638e199b"
}
Hope this helps someone out there
Side Note: I'm sending a POST request to domain.com/auth/authenticate instead of api.domain.com/auth/authenticate because I could not get domain.com/dashboard to recognize sentinel's cookie if I posted to api.domain.com. I've tried changing domain in config/session.php to .domain.com but still nothing. Maybe I'm doing something wrong?

Cordova app using angular & ADAL

I'm building my first mobile app using Cordova. The back-end services live on Azure so I'm trying to get authentication working by using the ADAL plugin for Cordova.
First of all I found out that the library does not do intercepts as the ADAL library for Angular does. I'm using Angular within my Cordova app, paired with material design directives for the look-and-feel. Would have been nice to have interception, but as I understood it's just not there at the moment (should find out how hard it is to implement).
So instead I now wrote a service which will take care of sending REST api requests to Azure, including the correct authentication token. It's based on the sample found here.
This is what I came up with:
var request = function(url)
{
createContext()
.then(function () {
getAuthToken().then(
function(token) {
sendRequest(token, url);
})
},
function (err) {
$log.error("Failed to create a context.");
});
};
First it will create the authentication context:
function createContext () {
return $q(function (resolve, reject) {
var authenticationContext = Microsoft.ADAL.AuthenticationContext;
authenticationContext.createAsync(authority)
.then(function (context) {
authContext = context;
$log.log("Created authentication context for authority URL: " + context.authority);
resolve();
}, function (err) {
$log.error("Failed to create authentication context: " + pre(err))
reject();
});
});
};
The using the context it should get the authentication token:
function getAuthToken()
{
if (authContext == null) {
$log.error('Authentication context isn\'t created yet. Create context first');
return;
}
return $q(function (resolve, reject) {
authContext.acquireTokenAsync(resourceUrl, appId, redirectUrl)
.then(function (authResult) {
resolve(authResult.accessToken);
}, function (err) {
$log.error("Failed to acquire token: " + pre(err));
reject();
});
});
}
And afterwards it should send the request but I'll leave that part out since it never gets there anyway. I feel the need to re-emphasize that I'm a complete n00b at this stuff, so please be easy on me and especially on the code. There's probably a lot of room for improvement, I get that.
When I actually run this, it pops up a window where I need to login using my Microsoft account, cool. I even got two factor authentication first time I tried this, very nice! So I log in and I get returned to the code. But now the authresult variable has a status of "Failed" and there's no access token in the result. Unfortunately there's also no indication of what went wrong. So first part of the question is; what could have gone wrong here?
Now we get to the second part of the question; how do you properly debug these kinds of things? On my desktop I'd run Fiddler to check out the communication, but I don't know how to do that for Android. I'm debugging on my device btw, cause for some reason all of the emulators available to me are extremely slow (VS and Google) even though my hardware specs should support them just fine.
Thanks for any pointers!
Update 03-02-2016
Fiddling around with the code a bit, I decided to pack things in a login function which gives a somewhat shorter sample:
var createContext = function () {
if (authContext == null) {
authContext = new Microsoft.ADAL.AuthenticationContext(authority);
}
};
var getAuthToken = function () {
if (authContext == null) {
$log.error('Authentication context isn\'t created yet. Create context first');
return;
}
return $q(function (resolve, reject) {
authContext.acquireTokenAsync(endpointUrl, appId, redirectUrl)
.then(function (authResult) {
resolve(authResult.accessToken);
}, function (err) {
$log.error("Failed to acquire token: " + pre(err));
reject();
});
});
}
var login = function () {
createContext();
getAuthToken();
}
This code runs on the following input vars:
var authority = 'https://login.windows.net/[tenantid]';
var resourceUrl = 'https://graph.windows.net/';
var appId = '1ef41b17-0943-4359-bc12-014f4fd2d841';
var redirectUrl = 'http://MyApp';
I now used chrome://inspect to see what is going over the wire. And to my big surprise, I see a valid SAML token returned from Azure. It has got my name in it and everything, which I'd recon they wouldn't send after a failed authentication. So it seems that even though the response is ok, the ADAL library doesn't give me a proper response (Status = Failed). Again no clue on how to proceed :S
I just solved it. And like one would expect, the remedy is as simple as they get. Configuring the application in Azure AD, I chose the "web application" type application, since this is a web application with Angular and all. Now I guess since Cordova translates things to native code, that's not the correct option to chose. As soon as I created a new application as "native application" instead and used the client ID of that one, everything started working.... Sincerely hope this will help someone else in the future...!
I had a very similar issue where I was trying to access a web api from a Cordova app. I was using the App ID Uri for the web api I wanted to access as the resouceURL when calling acquireTokenAsync. When I changed this to the client Id of the Web Api instead it worked.

Resources