I'm working on an SPA with Vue.js and vue-router and I'm now dealing with authorization/authentication using JWT. I have the back end (API endpoint) sorted out, such that it will issue a token in response to a login and check for the requisite header on subsequent requests. I now want to implement the client (Vue.js) side.
As I understand it, fundamentally what I need to do is to require authentication for all routes apart from '/' and '/login'. If authentication is present then I submit the token (which is stored in localStorage after a successful login) in an Authorization header. If it fails to validate successfully on the server, then the user will be redirected to '/login' as a result of the error response.
So, I have a couple of questions about what I need to do to implement this:
How do I best submit a header with every request, apart from to the login endpoint? I know with JQuery, which I'm using for AJAX, I can configure a global 'ajaxSetup' which will cause the header to be submitted with each request, but how can I specify exceptions? It's cumbersome to individually add the header to each API endpoint request.
Similarly, how to I set up an authentication pre-check which applies to all routes apart from the 2 mentioned ('/' and '/login')?
Given that I'm using the presence or otherwise of apparently valid authentication (apparently because it still has to be validated on the API endpoint) to determine whether or not to show certain menu items, etc., is it feasible to make this more granular and show different things for different permission levels, as determined by the 'scope' field in the token payload? Clearly the simplest way of dealing with a JWT token is purely to determine whether it is present or not, so no parsing of content is required at the client end. But given that JWT does allow meaningful content, is it a bad idea to try to make use of that meaning on the client side as well as server? Obviously this becomes less practical if the token is itself encrypted, so my idea would be to use unencrypted tokens (and ensure nothing of any consequence is exposed in the payload).
You can do global headers, and when the user is authenticated, add to the global headers the token like this example , I'm using Axios.
axios.defaults.headers.common['Authorization'] = "Bearer"+ authtoken.token
For checking the authentication of the user or to manage parts of your website, simply add global variable, and when the user is authenticated set the variable to true. Alternatively, use Vuex and it will be easy and the element you want to hide or to show to the user simply add v-if to the element like (example using Vuex)
<div v-if="this.$store.state.authenticated"> Hide Element from Guests </div>
And for the route, in your routes add meta field to indicate the path is requires authentication
{
path: '/dashboard',
component: DashboardPage,
name: 'Dashboard',
meta: {requiresAuth: true} // Meta Field , you can name it
}
In your Vue Router configuration add navigating guards that check the presence of the meta field, and if true check the if the user is authenticated
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
if(to.meta.requiresAuth) { // check the meta field
if(store.state.Authenticated) { // check if the user is authenticated
next() // the next method allow the user to continue to the router
}
else {
next('/') // Redirect the user to the main page
}
}
else {
next()
}
})
Related
Using Nuxt 2.15.3, with a Rails backend.
I'm trying to create a Google OAuth workflow in my app, and I'm having some trouble with the steps after getting the access code. Once the user authenticates with Google and gets redirected back with the access code in the URL params, I send a request to my own backend to save the access/refresh tokens in the User model.
NOTE: this google Auth flow is separate from my normal app sign-in/sign-up flow. I'm only using some Google APIs, so this has nothing to do with making a user account via Google OAuth, I'm only requesting access to some APIs in the user's Google account, namely the My Business API.
Now, my backend has a boolean google_authenticated field on the User table which gets set to true if the access/refresh token exists, which automatically gets sent to Nuxt as $auth.user.google_authenticated. This feature works fine, but my problem is that the page where the users get redirected to has a v-if that checks for this google_authenticated flag.
The template looks like this, obviously simplified for the sake of the question
<template>
<div v-if="googleAuthenticated">...</div>
<div v-else><a href="googleapis.com">Authenticate</button></div>
</template>
export default {
data() {
return {
googleAuthenticated: this.$auth.user.googleAuthorized,
};
},
async mounted() {
const accessCode = this.$route.query.code;
await this.$axios
.post("/users/google_oauth", {
access_code: accessCode,
})
.then((response) => {
this.$auth.fetchUser();
});
}
}
So as you can see, what I'm trying to do is to refresh the $auth.user.googleAuthorized automatically on mount when the user gets to the page with the code in the URL params. The problem is that $auth.user doesn't seem to be reactive, and the user needs to either navigate to another page or refresh the current page to get these changes to show up and for the v-if to trigger and to show the other div.
I can see from the dev console that the fetchUser() method does indeed get called, and I can see from the Vuex store that the auth/SET function has been called and that the $auth.user.googleAuthorized flag is set to true as well.
According to the Nuxt Docs, the $auth module is reactive, but I'm failing to see it. What can I do here to make these changes stick properly?
So it turns out it's a race condition (sorta).
What's happening is that once I tell my own API to get the access tokens from Google, that request obviously takes some non-zero amount of time. As such, Vue is waiting to get the 200 from my own API indicating that I've fetched the access token and can continue with using Google APIs.
So basically, by the time Nuxt is calling fetchUser() again, it's actually getting the user without the googleAuthorized flag set to true, because that just happens a tiny bit too slowly. It's not really something noticeable when you're just looking at dev tools though, since everything seems to be happening instantaneously more or less, but the timings are extremely close, which is the reason why it would sometimes work, and other times it wouldn't without a refresh.
Question: How can I enforce existing users to set up 2FA in .Net Core 3.1 Identity?
I have seen a couple of answers here already, but I have issues with them as follows:
Redirect user to set up 2FA page on login if they do not have it set up. Problem with this is that the user can simply jump to a different url to avoid this, therefore it is not actually enforced.
Have some on executing filter that checks if the user has 2FA enbaled or not and if not redirect them to MFA set up page. The issue I have with this is that on every single navigation the server must go to the database to check whether the user has this field enabled, thus creating a significant performance hit on each request. I know one trip to the database may not sound like much but I have worked with applications where this was the norm and other things used this method, causing a pile up of pre action db queries. I want to avoid this kind of behavior unless absolutely necessary.
My current idea is to on login:
Check the users credentials but NOT log them in
userManager.CheckPasswordAsync(....)
If the credentials pass, check if the user has 2FA enabled or not. If they do, continue through login flow, if not:
Generate a user token:
userManager.GenerateUserTokenAsync(.......)
and store this along with the username in a server side cache. Then pass a key to the cached items with a redirect to the 2FA setup page, which will not have the [authorize] attribute set, allowing users not logged in to access it.
Before doing anything on the 2FA set up page, retrieve the cached items with the provied key andverify the token and username:
userManager.VerifyUserTokenAsync(......)
If this doesn't pass, return Unauthorized otherwise continue and get the current user from the supplied UserName in the url that was passed via a cache key. Also dump the cached items and key so that should the url be snatched by a dodgy browser extension it can't be used again.
Continue to pass a new cache key to new user tokens and usernames to each 2FA page to authenticate the user as they navigate.
Is this an appropriate use of user tokens? And is this approach secure enough? I'm concerned that having the user not logged in presents security issues, but I think it is necessary in order to avoid the previously mention problem of going to the database on every request to check 2FA, as with this method trying to navigate away will just redirect to login.
I implemented this via a Filter Method
I have a BasePageModel which all my pages inherit
public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (!User.Identity.IsAuthenticated)
{
await next.Invoke();
return;
}
var user = await UserManager.GetUserAsync(User);
var allowedPages = new List<string>
{
"Areas_Identity_Pages_Account_ConfirmEmail",
"Areas_Identity_Pages_Account_ConfirmEmailChange",
"Areas_Identity_Pages_Account_Logout",
"Areas_Identity_Pages_Account_Manage_EnableAuthenticator",
"Areas_Identity_Pages_Account_ResetPassword",
"Pages_AllowedPageX",
"Pages_AllowedPageY",
"Pages_Privacy"
};
var page = context.ActionDescriptor.PageTypeInfo.Name;
if (!user.TwoFactorEnabled && allowedPages.All(p => p != page))
{
context.Result = RedirectToPage("/Account/Manage/EnableAuthenticator", new { area = "Identity" });
}
else
{
await next.Invoke();
}
}
I then changed both the Disable2fa and ResetAuthenticator pages to redirect to the main 2fa page
public IActionResult OnGet() => RedirectToPage("./TwoFactorAuthentication");
And removed the reset/disable links from that page
I chose to implement a more modern and OAuth friendly solution (which is inline with .Net Core Identity).
Firstly, I created a custom claims principal factory that extends UserClaimsPrincipalFactory.
This allows us to add claims to the user when the runtime user object is built (I'm sorry I don't know the official name for this, but its the same thing as the User property you see on controllers).
In here I added a claim 'amr' (which is the standard name for authentication method as described in RFC 8176). That will either be set to pwd or mfa depending on whether they simply used a password or are set up with mfa.
Next, I added a custom authorize attribute that checks for this claim. If the claim is set to pwd, the authorization handler fails. This attribute is then set on all controllers that aren't to do with MFA, that way the user can still get in to set up MFA, but nothing else.
The only downside with this technique is the dev needs to remember to add that attribute to every non MFA controller, but aside from that, it works quite well as the claims are stored in the users' cookie (which isn't modifiable), so the performance hit is very small.
Hope this helps someone else, and this is what I read as a base for my solution:
https://damienbod.com/2019/12/16/force-asp-net-core-openid-connect-client-to-require-mfa/
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/mfa?view=aspnetcore-5.0#force-aspnet-core-openid-connect-client-to-require-mfa
How can I pass the ui_locales from IdentityServer to clients after the Authorised Request has completed?
I've got multiple apps + Idsv4 under different domains (we can't share cookies). Example:
Mvc App1 (Main landing website)
Mvc App2
Identity Server
The user goes to MvcApp1 and changes the UiLocales on the screen (Eg. es-ES). I stored ui-Culture in the cookies
Then they click login and the AuthorisedRequest is sent to Idsv4 with ui_locales (es-ES).
Idsv4 receives the ui_locales and display the correct translated text (es-ES) with Login screen.
The user enters user credentials, successful and return to redirectUri
App1 can still show the correct text because it stores the locales in the cookies
When User naviagates to App2 and authorize request occurs, Idsv4 doesn't return the correct locales
so, App2 always show English text after successful authorization
The problem is that I can manage to pass the locales information between main app and idsv4 to display correct localized texts in both applications. But when the user navigate to second app (we've got more than 5 apps linked to idsv4), it's always set back to English text.
Please see the sequence diagram below:
As you can see above, since Idsv4 doesn't return ui_locales information (even though it does display correct translation text on its own), second app couldn't detect which language to display and fall back to default language - English.
I passeed the culture info per following in Startup.cs of App1. But ctx.ProtocolMessage.UiLocales is always null on OnAuthorizationCodeReceived event.
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = ctx =>
{
ctx.ProtocolMessage.UiLocales = Thread.CurrentThread.CurrentUICulture.Name;
return Task.CompletedTask;
},
OnAuthorizationCodeReceived = ctx =>
{
Console.WriteLine("OnAuthorizationCodeReceived");
return Task.CompletedTask;
},
....
}
So, I am wondering is there anyway to embed ui_locales values after the successful authorization in Idsv4.
Or Idsv4 is not doing like that because it's not the responsibility of Authentication Service?
Do I have to pass the locale information when the user navigates to other Apps to workaround this problem?
Example: https://ttcgApp1.com/?ui=es-ES, https://MyApp2.net/?ui-es-ES
Could you please help?
IdentityServer does not send any information back to the client, so best solution may be to add a claim that tells which locale was used at the time of login.
Please note that the token does contain additional information about the login process, e.g. the used flow: "amr" = [ "pwd"].
Information that can be used to limit access on the client, e.g. requiring 2FA.
Since OnAuthorizationCodeReceived is only called after succesful login, that's the one place where you can read the claim and use it to set the locale accordingly to the user locale in IdentityServer.
I use JWT token in my project and i have an Api route that i want to remove token that send from user.
I searched a lot and many people say to put my route in $except array in VerifyCsrfToken class , but it does not work.
I want this solution because if user send request with token, the result that returned is differ from result that returned without token.
///edited
I have one route without middleware and guard, in Product model i wrote a global scope that affect on is_active column.
public function apply(Builder $builder, Model $model)
{
if (Auth::guard('manager')->check()) {
return $builder;
}
if (Auth::guard('customer')->check()) {
return $builder->where('is_active', '=',true);
}
return $builder->where('is_active', '=',true);
}
if manager use this route for products, all products returned.
i want the user's role have no effect on output.
in other word i want to remove token from some api routes
I am not sure I fully understand your question.
But isn't it possible to just adequately add one layer of middleware and do your filtering there?
So you could check if the user has a token or not and react accordingly (like forwarding the user on a specific route).
In case that may help, you can find a few middleware related videos here explaining how it works.
VerifyCsrToken is for CSRF and it's different with JWT token.
In this situation you have 2 different solution:
When your user is sending JWT token, It means your user was authenticated, and you can get user's object and send proper response.
You could make 2 different controller, and two routes, one of them for without token and another one with token, And according to user request, make your proper response.
I wanted to allow my APIs to be access through a single key only. This API key will be in my environment variable file. Every time user will try to access my API. I'll first check whether key
present in the request body or not. After that I will check key matching with my fixed key. If both the condition will get satisfied I'll call api otherwise will response with 401 error.
I tried looking for middleware but not found any solution for this situation. Passport can be used
but it is asking for username and password for basic/local/digest strategy, Oauth and OpenId are based on third party provider.
I solved this by adding key check functionality in my controller itself.Like router directly route to
my API and after getting into the API controller I am call key authentication function. But it seems not to be standard way.
Instead I wanted to stop user at route lever itself.
Please share appropriate solution if anyone come across such situation.
If you really just need to check the key it should be super easy to do in a route middleware, eg:
var checkApiKey = function(req, res, next) {
if (req.body.apiKey !== process.env.API_KEY) {
return res.status(401).json({status: 'error'});
}
next();
};
app.all('/api/*', checkApiKey);
app.get('/api/something', function(req, res) {
return res.json({status: 'ok', message: 'You knew the key! Congrats!'});
});