I am creating an app using Laravel and building a small internal API to connect to with an Angular frontend.
I have the auth working, but wanted to ensure that this is an acceptable way to log in a user, and to make sure everything is secure.
Sessions Controller:
public function index() {
return Response::json(Auth::check());
}
public function create() {
if (Auth::check()) {
return Redirect::to('/admin');
}
return Redirect::to('/');
}
public function login() {
if (Auth::attempt(array('email' => Input::json('email'), 'password' => Input::json('password')))) {
return Response::json(Auth::user());
// return Redirect::to('/admin');
} else {
return Response::json(array('flash' => 'Invalid username or password'), 500);
}
}
public function logout() {
Auth::logout();
return Response::json(array('flash' => 'Logged Out!'));
}
Laravel Route:
Route::get('auth/status', 'SessionsController#index');
Angular Factory:
app.factory('Auth', [ "$http", function($http){
var Auth = {};
Auth.getAuthStatus = function() {
$http({
method: "GET",
url: "/auth/status",
headers: {"Content-Type": "application/json"}
}).success(function(data) {
if(!data) {
console.log('Unable to verify auth session');
} else if (data) {
console.log('successfully getting auth status');
console.log(data);
// return $scope.categories;
Auth.status = data;
return Auth.status;
}
});
}
return Auth;
}
]);
I would then essentially wrap the whole app in something like an "appController" and declare the 'Auth' factory as a dependency. Then I can call Auth.getAuthStatus() and hide / show things based on the user state since this will essentially be SPA.
I realize I also need to hide the /auth/status URI from being viewed / hit by anyone, and was wondering how to do that as well. Kind of a general question but any insight would be greatly appreciated. Thanks.
Great question. I've answered this same question before so I will say the same thing.
Authentication is a bit different in SPAs because you separate your Laravel app and Angular almost completely. Laravel takes care of validation, logic, data, etc.
I highly suggest you read the article linked at the bottom.
You can use Laravel's route filters to protect your routes from unauthorized users. However, since your Laravel application has now become an endpoint only, the frontend framework will be doing the heavy lifting as far as authentication and authorization.
Once you have route filters set, that doesn't prevent authorized users from attempting to do actions that they are not authorized to do.
What I mean by the above is for example:
You have an API endpoint: /api/v1/users/159/edit
The endpoint is one of the RESTful 7, and can be used to edit a user. Any software engineer or developer knows that this is a RESTful endpoint, and if authorized by your application, could send a request with data to that endpoint.
You only want the user 159 to be able to do this action, or administrators.
A solution to this is roles/groups/permissions whatever you want to call them. Set the user's permissions for your application in your Angular application and perhaps store that data in the issued token.
Read this great article (in AngularJS) on how to authenticate/authorize properly using frontend JavaScript frameworks.
Article: https://medium.com/opinionated-angularjs/techniques-for-authentication-in-angularjs-applications-7bbf0346acec
Related
I'm developing a Laravel + Angular app and i'm getting 401 Unauthorized in only 1 GET request.
Here I explain how I developed my authentication and how it work on Backend and Frontend. I wish you can help me.
I use Laravel Sanctum for manage authentication in my app. Here is how I program the backend.
I get users from my BD table:
Note: I have created a separate controller, to separate the authentication functions from the user functions, even so, I have tried to put this function in my AuthController and it has not given me any result.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
class UsersController extends Controller
{
public function getAllUsers()
{
return User::all();
}
}
As I want you to only be able to retrieve all the DB users if you are authenticated, in my api.php file I put the path inside the middleware:
Route::middleware('auth:sanctum')->group(function()
{
Route::post('logout', [\App\Http\Controllers\AuthController::class, 'logout']);
Route::get('getAuthUser', [\App\Http\Controllers\AuthController::class, 'getAuthUser']);
//Admin actions
Route::post('createUser', [\App\Http\Controllers\AuthController::class, 'createUser']);
Route::get('getAllUsers', [\App\Http\Controllers\UsersController::class, 'getAllUsers']);
});
If I make the request from the Postman everything works correctly, if I am not authenticated it gives me an error and if I have previously authenticated it returns all the DB users just as I expected. By the way, I am using cookies to send the jwt to the Frontend.
The problem is when in my Angular app I request my backend with the GET method to retrieve these users and display them in a table. In addition, the code to retrieve the users is within a condition in which it is looking at whether the user is authenticated or not. The truth is that I do not understand what may be happening.
getUsers(): void
{
//Check if user is authenticated
this.http.get('http://localhost:8000/api/getAuthUser', { withCredentials: true }). subscribe(
(res: any) =>
{
Emitters.authEmitter.emit(true);
Emitters.roleEmitter.emit(res.role);
//Get all users
this.http.get('http://127.0.0.1:8000/api/getAllUsers', { withCredentials: true }). subscribe(
res =>
{
this.users = res;
}
)
},
err =>
{
Emitters.authEmitter.emit(false);
Emitters.roleEmitter.emit("none");
alert("You should be authenticated for this.");
}
);
}
The first request that you see above getAuthUser, makes the request to the Backend in the same way as the second request getAllUsers and the first one works perfectly and the second one does not, it is in which I get an err. I call the getUsers() method in the ngInit().
I hope I have explained myself well. Any information you need to know let me know. Thank you.
The solution was in the request that gave the error to change the path of the api, instead of putting 127.0.0.1 putting localhost.
I’m building a React app where a key part of the functionality is a user can sign into their Google account and then access a feed of their most recent Google Drive/Docs mentions and notifications. A user arrives at my site where I load the Google OAuth2 client with my client_id, apiKey, scope and discoveryDocs, and they can click a button to sign in. For convenience, I’d like the user to not have to re-login and re-auth with their Google account every time they use the app or the app refreshes, I’d like the login information to be saved across sessions. For this I’ll use localStorage to start but eventually integrate a database like Firebase.
After looking through the JavaScript client Google OAuth2 docs I understand how most things work - understand the data and methods stored in the GoogleUser, GoogleAuth, etc objects. I’m having a little trouble with access and refresh tokens. I recognize that you can get the authenticated user’s information through gapi.auth2.getAuthInstance().currentUser.get() and gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse() returns an object with a lot of what I think I need like id_token, access_token and metadata like expires_at and token_type. I also see the grantOfflineAccess() method from which I extract response.code, but I’m not quite sure which of these tokenized strings is the right one to use and how I need to use it.
This FAQ from Google (https://developers.google.com/api-client-library/javascript/help/faq) is somewhat helpful but advises to Refresh the token by calling gapi.auth.authorize with the client ID, the scope and immediate:true as parameters., but gapi.auth.authorize is noted by Google in the client JS OAuth2 library as being incompatible with the more widely used and heavily documented api.auth2.init and signIn.
I also have a vague idea from posts like Google OAuth2 API Refresh Tokens that I need to follow server-side OAuth2 instructions and I can only get this refresh_token through a server-side call, but I’m still at a bit of a loss. I’ll caveat and say I’m more of a front end developer/designer so I'm shaky on my node and server-side skills.
TL;dr: I don't know how to keep my users who signed in via Google OAuth2 signed in after a refresh. I have an idea it's due to refresh_token and access_token and I have access to them but I don't know what to do after that, in terms of sending data to Google servers, getting information back, and setting the token information for the given user when they return.
Here's my method that calls on componentDidMount (basically when my app first loads):
loadGoogleClient = () => {
gapi.load("client:auth2", () => {
gapi.auth2.init({
'client_id': my-client-id,
'apiKey': my-key,
'scope': "https://www.googleapis.com/auth/drive.readonly",
'discoveryDocs': ['https://content.googleapis.com/discovery/v1/apis/drive/v3/rest']
})
// Listen for sign-in state changes.
console.log(`User is signed in: ${gapi.auth2.getAuthInstance().isSignedIn.get()}`);
gapi.client.load("https://content.googleapis.com/discovery/v1/apis/drive/v3/rest")
.then(() => { console.log("GAPI client loaded for API");
}, (error) => { console.error("Error loading GAPI client for API", error);
});
console.log('Init should have worked');
});
}
And here's my code that's onClick on my Signin button:
authGoogle = () => {
gapi.auth2.getAuthInstance()
.signIn({scope: "https://www.googleapis.com/auth/drive.readonly"})
.then(function() { console.log("Sign-in successful"); },
function(err) { console.error("Error signing in", err); });
}
If you are using the client lib (the gapi api) there is no need for a refresh token... Once logged in it should persist across sessions and refreshes... The issue is the code...
1) Include this in your index.html in the head section:
<script src="https://apis.google.com/js/api.js"></script>
2) Here is a component that will handle auth using the gapi lib and render a button conditionally (The code is self-explanatory but if you have a question just ask...)
import React from 'react';
class GoogleAuth extends React.Component {
state = { isSignedIn: null };
componentDidMount() {
window.gapi.load('client:auth2', () => {
window.gapi.client
.init({
clientId: '<your client id here...>',
scope: 'email', // and whatever else passed as a string...
})
.then(() => {
this.auth = window.gapi.auth2.getAuthInstance();
this.handleAuthChange();
this.auth.isSignedIn.listen(this.handleAuthChange);
});
});
}
handleAuthChange = () => {
this.setState({ isSignedIn: this.auth.isSignedIn.get() });
};
handleSignIn = () => {
this.auth.signIn();
};
handleSignOut = () => {
this.auth.signOut();
};
renderAuthButton() {
if (this.state.isSignedIn === null) {
return null;
} else if (this.state.isSignedIn) {
return <button onClick={this.handleSignOut}>Sign Out</button>;
} else {
return <button onClick={this.handleSignIn}>Sign in with Google</button>;
}
}
render() {
return <div>{this.renderAuthButton()}</div>;
}
}
export default GoogleAuth;
Now you can simply use this component/button anywhere in your app... Meaning if you have a Navigation component simply import it there and use it as a button login / log out...
I created a "user" named model with base class "User". I'm trying to login a user in Angular App using lb-ng generated service but it's not logging in.
In my Login controller I invoked User.login() providing email and password but its giving some weird error.
Even I included this code in my app.js
// Use a custom auth header instead of the default 'Authorization'
LoopBackResourceProvider.setAuthHeader('X-Access-Token');
// Change the URL where to access the LoopBack REST API server
LoopBackResourceProvider.setUrlBase('http://.../api');
In loginController
console.log(User.login({ email: "shah.khokhar#hotmail.com", password: "12345" }));
But it's giving this validation error:
Kindly help me on this.
Thanks,
If you could post your user.json file and your actual angular code then it would be more clear. But as far as I can see, there are things you are doing wrong.
You are making a request to User model instead of your custom user model which obviously won't work as your user data is present in your custom model and not the built in User model
You are most probably making a POST request to a wrong method than login method as login method request url looks something like this
http://localhost:3000/api/users/login
Here's a working sample code for login function which I use for my project
self.login = function () {
var data = {
email: self.email,
password: self.password
};
users.login(data).$promise
.then(function (user) {
$state.go('home');
})
.catch(function (err) {
$state.go('auth.register');
});
};
Hope this helps.
I am trying to use IdentityServer with an Angularjs application. The Angularjs front end is meant to call the authorize endpoint when an unauthenticated user attempts to navigate to the root url of the application. The app.config has a resolve property which will generate a routeChangeError. This event triggers a redirect to the OAuth2 Authorization endpoint (IdentityServer in my case, running on Asp.Net MVC).
baseballApp.config(['$routeProvider', '$locationProvider', '$httpProvider', function($routeProvider, $locationProvider, $httpProvider) {
$routeProvider.when('/players', {
resolve: {
auth: function ($q, $rootScope) {
var deferred = $q.defer();
if ($rootScope.token) {
deferred.resolve();
} else {
deferred.reject();
}
return deferred.promise;
}
},
templateUrl: '/FrontEnd/Templates/PlayerList.html',
controller: 'PlayerListController'
});
$routeProvider.otherwise({ redirectTo: '/players' });
var spinnerFunction = function (data, headersGetter) {
if (data) {
$('#spinner').show();
return data;
}
};
$httpProvider.defaults.transformRequest.push(spinnerFunction);
$httpProvider.interceptors.push('authInterceptor');}]).run(['$rootScope', '$location', 'authenticationService', function ($rootScope, $location, authenticationService) {
//authenticationService.getBearerToken().then(function(data) {
// $rootScope.token = data;
//});
debugger;
var generateRandomString = function() {
return (Math.random().toString(16) + "000000000").substr(2, 8);
}
var nonce = generateRandomString();
var state = generateRandomString();
$rootScope.$on('$routeChangeError', function () {
window.location = 'https://localhost:44301/identity/connect/authorize?client_id=baseballStats&redirect_uri=https%3a%2f%2flocalhost%3a44300%2f&response_type=id_token+token&scope=openid+profile+roles+baseballStatsApi&nonce=' + nonce + '&state=' + state;
});}]);
As you can see I redirect to the authorize endpoint if I don't have a token. However, it's not clear to me how I can retrieve the tokens after I have signed in on the authorization endpoint.
I have specified a return url, and after sign in it does redirect to the application, but how would I set up my angular code to retrieve the token that is supposed to be generated on the Authorize endpoint and sent back to the application?
I have seen many examples of people using bearer token authentication for angular, but I have not seen anyone use the Authorize endpoint of an OAuth2 server with Angular. In MVC, there are many built in callbacks to retrieve the token. I am looking for some ideas on how to implement this in angular.
Could you please give me a hint as to what I'm doing wrong?
EDIT
I have also tried to pass a hash navigation tag in the redirect uri, but get an "Error unknown client" from IdentityServer. Please see the code below
$rootScope.$on('$routeChangeError', function () {
var returnUrl = encodeURIComponent('https://localhost:44300/#/players');
window.location = 'https://localhost:44301/identity/connect/authorize?client_id=baseballStats&redirect_uri=' + returnUrl + '&response_type=id_token+token&scope=openid+profile+roles+baseballStatsApi&nonce=' + nonce + '&state=' + state;
});
Anybody know why?
I presume you're using the implicit flow grant for your spa js app?
In that case, the identity server, after you've logged in, will send the access token back to a call back url of your spa app. Using that call back page you are able to capture to token in order to pass it along to your resource server within the authorization header as a bearer token.
Checkout this video from Brock Allen, very well explained:
https://vimeo.com/131636653
By the way, you should use the oidc-token-manager, written by Brock himself. It is very easy to use and it abstracts away all communication with the identity server. It's also available on bower so you can grab it from there.
Finally I would like to point you to my own spa angular app I've been working on lately: https://github.com/GeertHuls/SecuredApi.
It actually uses the token manager in order to obtain access tokens, auto-refresh them an expose them throughout the entire angular app.
Hope this will inspire you a little in order to integrate this with angular.
I sidestepped around this issue by creating a custom ActionResult which would return a JSON response with a token when the user was already logged in via .NET Auth cookies, it's a bit hacky and looks like this;
[AllowAnonymous]
public ActionResult GetToken()
{
if (!User.Identity.IsAuthenticated)
{
Response.StatusCode = 403;
return Json(new {message = "Forbidden - user not logged in."}, JsonRequestBehavior.AllowGet);
}
var claims = new ClaimsPrincipal(User).Claims.ToArray();
var identity = new ClaimsIdentity(claims, "Bearer");
AuthenticationManager.SignIn(identity);
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var token = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
return Json(new { token }, JsonRequestBehavior.AllowGet);
}
In Aurelia, there doesn't seem to be any support for CSRF protection yet, as opposed to AngularJS's XSRF-TOKEN header which is set automatically on all XHR requests by the AngularJS framework.
How should I go about protecting an Aurelia app from CSRF attacks? Should I roll my own support based on the OWASP CSRF Prevention Cheat Sheet, or are there any alternatives out there for Aurelia already?
You should be able to do this yourself fairly easily by using Aurelia's HTTP interceptors (see examples in the docs). Before every request, you can send your token. This can be done with both the conventional aurelia-http-client and the new standard aurelia-fetch-client.
Your code might look like this:
export class MyRestAPI {
static inject () { return [HttpClient]; } // This could easily be fetch-client
constructor (http) {
this.http = http.configure(x => {
x.withBaseUrl(myBaseUrl);
x.useStandardConfiguration();
x.withInterceptor({
request: function (request) {
request.headers.set('XSRF-TOKEN', myAwesomeToken);
return request;
}
});
});
}
...
}
On every request, your token would be sent. You'd have to handle the validation on the server side. You could easily set up your code so that your initial request could grab a token, or you could pass a token back as part of your authentication payload, or if you wanted to you could even store a token in the browser's localstorage and use it that way.
You could even go a step further and implement JWT authentication. If you're using node.js, I have a small blog post that describes how I implemented JWT in Express. There's a plugin on Github called aurelia-auth that handles JWT, and there's a blog post on its implementation on the Aurelia blog as well.
Here is a sample interceptor that reads the token from the response header if it exists and sets it automatically on every request that needs it.
import {Interceptor, HttpResponseMessage, RequestMessage} from "aurelia-http-client";
class CsrfHeaderInterceptor implements Interceptor {
private static readonly TOKEN_HEADER = 'X-CSRF-Token';
private latestCsrfToken: string;
response(response: HttpResponseMessage): HttpResponseMessage {
if (response.headers.has(CsrfHeaderInterceptor.TOKEN_HEADER)) {
this.latestCsrfToken = response.headers.get(CsrfHeaderInterceptor.TOKEN_HEADER);
}
return response;
}
request(request: RequestMessage): RequestMessage {
if (this.latestCsrfToken) {
if (['POST', 'PUT', 'PATCH'].indexOf(request.method) >= 0) {
request.headers.add(CsrfHeaderInterceptor.TOKEN_HEADER, this.latestCsrfToken);
}
}
return request;
}
}
You register it in your http/fetch client with for example:
httpClient.configure((config) => {
config
.withBaseUrl("/api/") // adjust to your needs
.withHeader('Accept', 'application/json') // adjust to your needs
.withHeader('X-Requested-With', 'XMLHttpRequest') // adjust to your needs
.withInterceptor(new CsrfHeaderInterceptor());
});