I would like to create a route guard for protecting routes against unauthorized users.
I am using jsonwebtoken for authorization, and at the moment storing that in localStorage.
My idea is, when a user wants to access a protected admin route, authguard sends the token for validation to the nodeJS/Express server that after validation returns a true or 401 (whether the user is admin) to the client side.
auth service:
isLoggedIn(){
let headers = new HttpHeaders().set('x-auth-token',localStorage.getItem('token') || '');
return this.http.post('http://localhost:3000/api/users/check-auth', {}, { headers: headers }).toPromise();
}
authGuard service:
canActivate(){
return this.sign.isLoggedIn().then(res => {return res;}).catch(ex => {return ex});
}
My purpose would be to avoid manually setting a token key in the localstorage by the user to see the guarded route, even if he would not be able to implement any XHR request.
Could you please verify if its a good or bad idea and come up with better solution on security side?
Many thanks!
A good practice would be to manage roles (or permissions) at the model level on the server-side. For example a User class could have a roles property, such as :
auth.service.ts
myUser.roles = ['ROLE_ADMIN']
This way, when your user logins, you can store the information in your auth.service.ts
// auth.service.ts
get isAdmin() {
return this.user.roles.includes('ROLE_ADMIN')
}
Note that usually you want to store this information in you app state management, whether it be plain rxjs, ngrx, ngxs...
Finally you would add an AuthInterceptor which would redirect your user if your API returns a 401.
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.
How to get JWT cookies in our react application, how to check the user is login or not I am unable to find how to handle my react application session.
I really appreciate who helps me out with this problem.
Thanks in advance
The server side API is setting the HTTPOnly cookie which you wont be able to read in JS.
What you need to do it in your react App handle a 401 status error and based on that set a flag isAuthenticated or something as false. Otherwise keep it to be true. With each request to the server HTTPOnly cookie would be sent automatically so you don't need to handle the token inside a cookie.
The backend code needs to send a 401 once the cookie is expired, or the logout is requested or the JWT inside a cookie expires.
Before I say anything, you have included app.use(cookieParser()) in index.js right? Because if not, you're gonna need that once you've installed it with npm i cookie-parser
But anyway, a few things:
You can create a PrivateRoute in React, as far as I'm aware this tends to work well to protect routes from unauthorized users.
You can simply store an isAuthenticated in either the state or localStorage: however this will require that you make absolutely sure that a user shouldn't be able to just change the value in the state or add isAuthenticated in localStorage and just spoof authenticity (this is the part that took me the longest to get right).
Anyway, the way I've handled this is that when a user logs in an access token (and a refresh token if it doesn't already exists) are generated from the server and sent to the client in an httpOnly cookie, while this makes it so that you can't do anything with it on the client side with JavaScript as Pavan pointed out in his answer (which is generally a good thing), you should use res.status for validation when you make the fetch request. Here's what my fetch request kind of looks like:
const login = async (user) => {
const body = JSON.stringify(user);
return fetch(loginURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'include', //this is important
body: body
}).then(function(res) {
if (res.status === 200) {
const id = Date.now();
localStorage.sid = id; //this is temporary
return res.json()
} else {
return res.json()
}
})
// you can ignore this part
.then(async resp => {
return resp
})
}
Side-note: Your browser automatically handles the httpOnly cookies you send from your server however the credentials: 'include' needs to be included in your subsequent fetch requests.
In one of my main parent components, the login function is called:
login = async (user) => {
this.setState({ error: null });
await adapter.login(user).then(data => {
if (!data.error) {
this.setState({session: "active"})
} else if (data.error && data.error.status === 401) {
// this is so I can handle displaying invalid credentials errors and stuff
this.setState({ error: data.error, loading: false });
}
});
}
I also have a middleware on the server-side that is run before any of the code in the routes to verify that the user making the request is actually authorized. This is also what handles access token expiration for me; if the access token has expired, I use the refresh token to generate a new access token and send it back in the httpOnly cookie, again with a status of 200 so the user experience isn't jarring. This does of course mean that your refresh token would have to live longer than your access token (I haven't decided how long in my case yet, but I'm thinking either 7 or 14 days) though as far as I'm aware that is okay to do.
One last thing, the parent component I referred to earlier calls a validate function which is a fetch request to the server within its componentDidMount so that the user is verified each time the component mounts, and then I've included some conditional rendering:
render() {
return (
<div className="container">
{
!localStorage.sid && <LoginForms {...yourPropsHere}/>
}
{
this.state.loading && <Loading />
}
{
localStorage.sid && this.state.session === "active" && <Route path="/" render={(props) => <Root error={this.state.error} {...props}/>}/>
}
</div>
);
}
I've gone the conditional rendering route as I couldn't get PrivateRoute to work properly in the time that I had, but either should be fine.
Hopefully this helps.
Your login API should return JWT token and how long it should be live.
Your login API response would be like :
{
jwt: your jwt token,
duration: in seconds
}
Use universal-cookies NPM to store this result into cookies.
For more details how to manipulate with cookies, visit
https://www.npmjs.com/package/universal-cookie
For setting cookies your code like:
const cookies = new Cookies();
cookies.set(name of cookies, jwt value from API call, {
maxAge: duration,
});
Above code store the jwt cookies in browser and after maxAge automatically remove it from browser.
So for identifying session is present or not, you should check after specific interval cookies has present in browser or not. If cookies has present in browser then session is on, otherwise session has expired.
I'm adding OAuth into my github clientside application. I have the final auth token being returned successfully, but I feel like I hacked my workflow.
Current Architecture Flow:
1) User clicks href link from component to hit the initial OAUTH route
2) Retrieve token from Github for user identity
3) Github redirects to my server route and my server route sends an additional POST to the /access_token request page with the client_secret, id and code from the above step.
4) Finally I redirect from the above route back to my UI and set a URL parameter in the process
5) In the componentDidMount I strip the final auth token from the window.url and set it in my state
Note: I plan on storing the token in Redux later, but this is the base
level as to what I'm doing.
Actual Code
Server
app.get("/login", async (req, res) => {
// Get the identity token from GitHub origin
return await axios
.post("https://github.com/login/oauth/access_token", {
code: req.query.code,
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET
})
.then(async resp => {
// Little hack to parse out the query params into an object
let data = await url.parse("http://parse.com?" + resp.data, {
parseQueryString: true
}).query;
res.redirect(
url.format({
pathname: Environment.getUrl(),
query: {
token: data.access_token
}
})
);
});
});
UI Authentication Component
export default class GithubAuthentication extends React.Component {
state = {
authToken: "DATA"
};
componentDidMount() {
let currUrl = window.location.href;
this.setState({ authToken: currUrl.split("token=")[1] });
}
render() {
return (
<React.Fragment>
<a href="https://github.com/login/oauth/authorize?client_id=b5cd37110eb31620aad7">
{this.state.authToken ? "Logout" : "Login With Github"}
</a>
<span>{this.state.authToken}</span>
</React.Fragment>
);
}
}
Questions
1) The one thing I wasn't able to figure out was to make the href link a controlled component and actually hit the auth URL with something like SuperAgent or Axios. Instead, I'm forced to use this href link, not sure why.
2) Is this actually a sensible flow for getting the final auth token?
Regarding question 2, from a security standpoint, it is better to keep access token on server-side and never send the token to client-side.
I couldn't find good written resources, so I'd like to share this video which sums up how to deal with access token clearly.
https://www.youtube.com/watch?v=CHzERullHe8&list=PL78z2Z3ZApYcKb-GDQt6ikdN2EDqmKJrT&index=12&t=419s
Take away from the video
We still don't have a good way to securely store the token on the browser
By storing the access token on the server-side and using session cookie, you can minimize the risk of access token being compromised.
To actually implement this flow, you can use cookie-session to generate session. You can also use github-passport to simplify the implementation.
https://github.com/expressjs/cookie-session
https://github.com/jaredhanson/passport-github
1) I think you should reorganize your app so that you can use a component instead of an href link. You would know whether you're authenticated or not based on the value on the state property. This value can be passed as prop to your component which is where you would put the logic of authenticated ? "Logout" : "Login" or anything else.
2) the flow is OK but you have to make sure you do server side validation of the token since it's easy to just flip the switch on the UI and the pretend you're authenticated very easily.
I am authenticating my Single Page App (Angular4) with Azure AD, and using Adal.js for the same. On the login page, I click a button that redirects to Microsoft AAD and upon successful login it redirects back to application home page, and receives id_token and user info from JWT.
I need the access_token for back-end API access, which I am trying to acquire through the the ADAL AuthenticationContext's getCachedToken() method, and sending the clientId as parameter:
this.context.getCachedToken(this.configService.AdalConfig.clientId)
But this method returns the same token which is stored in session storage as id_token (adal.idtoken). It basically creates a new item in session storage by with a concatenated key, which has same value as id_token
adal.access_token.key + clientId = id_token
ex: adal.access_token.key239f6fc7-64d2-3t04-8gfd-501efc25adkd = <id-token-value>.
I also tried to fetch access_token with AuthenticationContext.acquireToken() method, but it too gave the id_token back.
Where am I going wrong?
EDIT: posting the code.
I am calling the function login(), and after successful login, trying to get the access token in home page via get accessToken() property accessor in adal.config.ts.
config.service.ts
import { Injectable } from '#angular/core';
#Injectable()
export class ConfigService {
constructor() {}
public get AdalConfig(): any {
return {
tenant: 'common',
clientId: <application-id>,
redirectUri: window.location.origin + '/',
postLogoutRedirectUri: window.location.origin + '/'
};
}
}
adal.service.ts
import { ConfigService } from './config.service';
import { Injectable } from '#angular/core';
import { adal } from 'adal-angular';
let createAuthContextFn: adal.AuthenticationContextStatic = AuthenticationContext;
#Injectable()
export class AdalService {
private context: adal.AuthenticationContext;
constructor(private configService: ConfigService) {
this.context = new createAuthContextFn(configService.AdalConfig);
}
login() {
this.context.login();
}
logout() {
this.context.logOut();
}
handleCallback() {
this.context.handleWindowCallback();
}
public get userInfo() {
return this.context.getCachedUser();
}
public get accessToken() {
return this.context.getCachedToken(this.configService.AdalConfig.clientId);
// return this.context.acquireToken(this.configService.AdalConfig.clientId, function(message, token, response) {
// console.log(message, token, response);
// });
}
public get isAuthenticated() {
return this.userInfo && this.accessToken;
}
}
Actually, after a bit of reading, turned out that connecting SPA's to Azure AD requires OAuth 2.0 Implicit Grant flow. The Microsoft documentation says:
In this scenario, when the user signs in, the JavaScript front end
uses Active Directory Authentication Library for JavaScript (ADAL.JS)
and the implicit authorization grant to obtain an ID token (id_token)
from Azure AD. The token is cached and the client attaches it to the
request as the bearer token when making calls to its Web API back end,
which is secured using the OWIN middleware.
So, it's the id_token itself that I need to send to the back-end APIs, which in turn can be validated and used. More info about validation is given here:
Just receiving an id_token is not sufficient to authenticate the user;
you must validate the id_token's signature and verify the claims in
the token per your app's requirements. The v2.0 endpoint uses JSON Web
Tokens (JWTs) and public key cryptography to sign tokens and verify
that they are valid.
You can choose to validate the id_token in client
code, but a common practice is to send the id_token to a backend
server and perform the validation there. Once you've validated the
signature of the id_token, there are a few claims you will be required
to verify.
I've faced an issue like yours when I was trying to send the token to a .Net Core API endpoint.
It worked for me when I sent the token from the adal.access.token.key node, on sessionStorage.
Using adal.access.token.key or adal.idtoken token values (they are the same) didn't work for me.
Valid token on adal.access.token.key node.
Setup is an Ember frontend with a rails backend using JSON api.
Everything is going fine but some questions do come up:
How do I ensure only the emberjs application consumes the api? I wouldn't want a scripter to write an application to consume the backend api.
It all seems pretty insecure because the EmberJS application would come in a .js file to the client.
How would I ensure a user is really that user if everyone has access to a JS console?
You can extend the RESTAdapter and override the ajax method to include your authentication token in the hash, and you need make sure your controllers validate that token.
In my environment (.NET), I have the authentication token in a hidden field of the document which my app renders, so my ajax override looks like this:
App.Adapter = DS.RESTAdapter.extend({
ajax: function(url, type, hash, dataType) {
hash.url = url;
hash.type = type;
hash.dataType = dataType || 'json';
hash.contentType = 'application/json; charset=utf-8';
hash.context = this;
if (hash.data && type !== 'GET') {
hash.data = JSON.stringify(hash.data);
}
var antiForgeryToken = $('#antiForgeryTokenHidden').val();
if (antiForgeryToken) {
hash = {
'RequestVerificationToken': antiForgeryToken
};
}
jQuery.ajax(hash);
}
});
The token can come from a cookie or whatever you define, as long as you're able to include it in the request header and have your controllers validate it (possibly in before_filter), it should enough.
Then in the Store, pass the new adapter instead of the default (which is RESTAdapter)
App.Store = DS.Store.extend({
revision: 12,
adapter: App.Adapter.create()
})
Note: RESTAdapter#ajax will be changed in favor or Ember.RSVP, making this override deprecated. It must be updated after the next release, but should be ok for revision 12.
I am using Ember Simple Auth to great effect for user authentication and API authorisation.
I use the Oauth 2 user password grant type for authentication of the user and authorising the application by way of a bearer token which must be sent on all future API requests. This means the user enters their username/email and password into the client app which then sends to the server via HTTPS to get an authorisation token and possibly a refresh token. All requests must be over HTTPS to protect disclosure of the bearer token.
I have this in app/initializers/auth:
Em.Application.initializer
name: 'authentication'
initialize: (container, application) ->
Em.SimpleAuth.Authenticators.OAuth2.reopen
serverTokenEndpoint: 'yourserver.com/api/tokens'
Em.SimpleAuth.setup container, application,
authorizerFactory: 'authorizer:oauth2-bearer'
crossOriginWhitelist: ['yourserver.com']
In app/controllers/login.coffee:
App.LoginController = Em.Controller.extend Em.SimpleAuth.LoginControllerMixin,
authenticatorFactory: 'ember-simple-auth-authenticator:oauth2-password-grant'
In app/routes/router.coffee:
App.Router.map ->
#route 'login'
# other routes as required...
In app/routes/application.coffee:
App.ApplicationRoute = App.Route.extend Em.SimpleAuth.ApplicationRouteMixin
In app/routes/protected.coffee:
App.ProtectedRoute = Ember.Route.extend Em.SimpleAuth.AuthenticatedRouteMixin
In templates/login.hbs (I am using Ember EasyForm):
{{#form-for controller}}
{{input identification
label="User"
placeholder="you#example.com"
hint='Enter your email address.'}}
{{input password
as="password"
hint="Enter your password."
value=password}}
<button type="submit" {{action 'authenticate' target=controller}}>Login</button>
{{/form-for}}
To protect a route I just extend from App.ProtectedRoute or use the protected route mixin.
Your server will need to handle the Oauth 2 request and response at the configured server token endpoint above. This is very easy to do, Section 4.3 of RFC 6749 describes the request and response if your server side framework doesn't have built-in support for Oauth2. You will need to store, track and expire these tokens on your server however. There are approaches to avoiding storage of tokens but that's beyond the scope of the question :)
I have answered the backend question and provided example rails example code for user authentication, API authorisation and token authentication here