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());
});
Related
So, I am new at node and decided to create a user authentication/authorization system. Btw, I am using ejs as view engine and not any front-end framework. Everything works when I use postman to check API. Users are registered, tokens are generated while logging in and most importantly authorization also works when I manually put generated token in header (using postman). But how could I extract those tokens and put them in headers without postman? I saw some implementations using axios, fetch etc. But all of them were using front-end frameworks to do that.
Logging in users
Accessing protected route without token
Accessing protected route with token
UPDATE:
So the problem was in passport.authenticate() method and I added my personal verification method to extract cookies specifically.
const verifyJWT = (req: Request, res: Response, next: NextFunction) => {
const signedToken = req.cookies.jwt;
if (signedToken) {
jwt.verify(
signedToken,
config.PRIV_KEY,
{ algorithms: ["RS256"] },
(err: any, decodedToken: any) => {
if (err) {
// tslint:disable-next-line:no-console
console.log(err);
} else {
// tslint:disable-next-line:no-console
console.log(decodedToken);
next();
}
}
);
} else {
res.send("Unathorized");
}
};
You don't have to use frameworks but you do need to use javascript. Whatever you do with postman you can do using fetch or axios, you can save the tokens in your cookie.
I am implementing a login feature to a website project. The backend is Express and the frontend is Nuxt 3. Upon successfully authenticating a user login, the Express backend returns necessary data to the webserver, which then creates an httpOnly cookie and sets any necessary data in a Pinia store. On page refresh, I would like the Nuxt 3 server to look at the cookie and setup the Pinia store (since it is lost on page refresh).
Can someone provide some guidance? I have looked at the useNuxtApp() composable, and I can see the cookie in nuxtApp.ssrContext.req.headers.cookie, but that only provides a K/V pairing of all set cookies, which I would need to parse. I know of the useCookie composable, but that only works during Lifecycle hooks, which seems to only resolve undefined.
Thanks.
Not sure if this is the right way,
but it's a solution I used to get through a similar case - dotnet api + nuxt3 client.
First, we need to proxy API (express in your case),
this will make it, so our cookie is on the same domain and browser will start sending it to /api/ endpoints.
Install #nuxtjs-alt/proxy - npm i #nuxtjs-alt/proxy.
Add configuration to nuxt.config.ts (my api running on localhost:3000):
nuxt.config.ts:
export default defineNuxtConfig({
modules: [
'#nuxtjs-alt/proxy'
],
proxy: {
enableProxy: true,
fetch: true,
proxies: {
'/proxy': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/proxy/, '')
}
}
}
});
Then we can the request that will set a cookie anywhere on client using proxy instead of a direct call.
Anywhere on client, do a request using newly setup proxy instead of calling API directly.
Adjust parameters based on your setup.
await $fetch('/proxy/user/sign-in', {
method: 'POST',
body: {
email: 'example#mail.com',
password: 'password'
}
});
Ultimately, should end up with a cookie set on our client domain.
And lastly, when we handle request client side - we read the cookie and set up on forwarding request.
Replace COOKIE_NAME and API URL accordingly.
server/api/user/me.get.ts:
export default defineEventHandler(async (event) => {
return await $fetch('http://localhost:3000/user/me', {
headers: {
Cookie: `COOKIE_NAME=${
getCookie(event, 'COOKIE_NAME')
}`
}
});
});
API call will use the same cookie we got when we did a first request using cookie and the server should be able to read it.
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.
There is an API behind api.sample.com and a browser-based webapp (Single-Page, Javascript-driven) behind app.sample.com as well as other, non-browser based clients.
The API supports Basic and Token-based authentication with the token packed into a cookie for browsers.
To make the setup work for browsers, CORS has to be enabled and withCredentials set:
var cors = new EnableCorsAttribute("https://app.sample.com", "*", "GET,POST,PUT,DELETE");
cors.SupportsCredentials = true;
cors.PreflightMaxAge = 3600;
config.EnableCors(cors);
Now I played around with CSRF vulnerabilities and found that AJAX is blocked by the origin setting, <img> or <a> don't see any response data, but <form>-submissions are open to exploits.
To protect forms, I'm adding an anti-forgery token to the mix and validate it for every request to a controller action that doesn't have the AllowAnonymousAttribute set.
I'd like to use the System.Web.Helpers.AntiForgery class, but it requires the principal identity to be set, which is not the case when validating credentials on login - only on subsequent requests.
So I came up with the idea that the browser client requests a CSRF-Token right after validating credentials, sends the token as a header on every request and the server validates the token in a FilterAttribute. E.g.
[HttpGet, Route("xsrf"), AllowAnonymous]
public IHttpActionResult GetCSRFToken()
{
// AllowAnonymous is set to allow requests without csrf token to reach this action
if (User.Identity.IsAuthenticated)
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
// or set a cookie
return Ok(cookieToken + ":" + formToken);
}
return Unauthorized();
}
and
public class ValidateXSRFTokenAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
// apply only if cookie-authentication and no AllowAnonymousAttribute set on the action
if (actionContext.RequestContext.Principal.Identity.AuthenticationType == DefaultAuthenticationTypes.ApplicationCookie &&
!actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
{
try
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (actionContext.Request.Headers.TryGetValues("X-XSRF-Token", out tokenHeaders))
{
var tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
catch
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
ReasonPhrase = "Anti-XSRF Validation Failed."
};
}
}
base.OnAuthorization(actionContext);
}
}
Is this implementation correct? Anything to improve?
It is still possible to make the server send the CSRF-token using a <form> or <iframe> submission, so the protection is depending on not being able to read that response in javascript according to same-origin-policies.
Or better roll my own CSRF-Token that can be set directly when logging in?
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