Good Day everyone,
I'm learning Laravel Passport and Vue.JS(standalone) at the same time.
I'm using Password Grant Token to authenticate the user.
I encounter this issue that the secret_key must be always hidden.
I have this Login Component in my vuejs where I need to add the client_secret as parameter to get access token. However, since VUEJS is a javascript framework. there's a way that someone can see the client_secret on the minified build file.
my question is that, is it just normal? is there a way to conceal the client_secret?
at first I don't mind the issue since I have implemented CORS on laravel where I can only select the allowedOrigins. My thinking is that it doesn't matter if they know the secret key as long as I can filter the allowedOrigins.
Here's my code in VUEJS
login(){
this.$validator.validateAll().then((result) => {
if (result) {
var data = {
client_id: 3,
client_secret: 'client-secret key',
grant_type: 'password',
username: this.inputs.email,
password: this.inputs.password
}
this.$http.post("oauth/token", data).then(response => {
this.$auth.setToken(response.body.access_token, response.body.expires_in + Date.now());
bus.$emit('reload');
this.$router.push('/');
})
}
});
}
Any advice will be appreciated.
Thanks in advance.
Laravel Passport has a built in way of allowing you to consume your own API with a Javascript application. It provides a simple middleware which you can add to your web middleware group (which you'll find in App\Http\Kernel):
'web' => [
// Other middleware...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
Laravel will check to see if you have a logged in user (via the standard cookie/session guard) and if so it will generate JWT for you and store it in a cookie. It will also check for this cookie's presence and validate it when you make requests to your API so you no longer need to pass an access token.
One thing to note however, is that you will need to ensure that you continue to pass your CSRF tokens with your requests (assuming you have CSRF protection turned on). If you're using Axios with Vue, you can make sure this happens very easily with the following:
window.axios.defaults.headers.common = {
'X-Requested-With': 'XMLHttpRequest',
};
With this approach you don't need to worry about access tokens at all or expose a client_id and secret to the client.
I faced the same problem and found an interesting solution.
You can add a custom endpoint on the backend and make the request from there.
All you have to do is to:
First, create a route in the api.php file Route::post('/login', 'AuthController#login');
Then, create the AuthController and login function associated with that route php artisan make:controller AuthController
Finally, install Guzzle, the HTTP client that will allow you to make a request from PHP composer require guzzlehttp/guzzle and make the request from the login function
public function login(Request $request)
{
$http = new \GuzzleHttp\Client;
try {
$response = $http->post('http://example.test/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 2,
'client_secret' => 'your_client_secret',
'username' => $request->username,
'password' => $request->password,
]
]);
return $response->getBody();
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
if($e->getCode() == 400)
{
return response()->json('Invalid Request, Please enter email or password.', $e->getCode());
}
else if($e->getCode() == 401)
{
return response()->json('Your credentials are incorrect. Please try again', $e->getCode());
}
return response()->json('Something went wrong on the server.', $e->getCode());
}
}
Now, the vue.js front end app juste needs to send a post request to http://example.test/login with the username and password to get back the access_token without knowing the client_secret since it is abstracted to the backend.
Here is the video that explains it and implements it really well.
And a presentation about some theory and how you can store and send the token from the vue.js app once you retrieve the token.
Hope this helps.
Related
So I'm trying to implement my own authorization server using spring. I have my front-end app which is implemented using ReactJs. One thing that I don't understand is that, why do I have to redirect to the login page on the authorization server side. I want to have nice and clean login page on the client side using ReactJs without redirecting to the other site. I mean, I trust my client (ReactJs app) anyway can I just use post request to make an authentication like this.
const login = () =>
// start loggin in
axios.post('/oauth/login_api_without_redirect',
{ username: 'usr',
password: 'pwd',
client_id: 'react_app',
... scope, state, etc...
code_challenge: sha_256('yyyy'),
code_challenge_method: 'sha_256' })
.then(r => {
axios.post('/oauth/token',
{ ... etc params ...
auth_code: r.data.authorizationCode,
code_verifier: 'yyyy' })
})
and on the authorization server side I would have this API.
#PostMapping("/oauth/login_api_without_redirect")
public String authorize(AuthenticationDTO authen) {
// include client_secret and other stuff..
return authorizationCode
}
Is that safe to do so.
I'm building a web app with CodeIgniter 4 where I implemented a REST API for register and login and a profile page just a test my login.
I have a login form that sends a javascript fetch post request to my api and I receives a jwt token. This is working.
Now I am in the situation where I think I did not understand the principle.
I want that the user stays logged in and doesn't need to re-login every time. The token expires after 12h.
And I want to use php (if possible) as the entire app runs on php.
Currently, I have a little javascript function to store my token:
const store = {};
store.setJWT = (data) => {
this.JWT = data;
};
But this is not secure against page reload.
Additionally I am creating a cookie with php, when the user logs in:
helper('cookie');
set_cookie([
'name' => 'login',
'value' => $token,
'expire' => $exp,
'httponly' => true,
'secure' => true,
]);
I am able to fetch data from the API using the cookie or the store object.
const token = getCookie('login');
const res = await fetch('/profile', {
headers: {
'Authorization': `Bearer ${token}` // or store.JWT
}
});
So.... what I want is:
The user goes to a protected url e.g. https://myapp.com/profile and if he is logged in, he has access. If not, he gets redirect to the login page.
Is using the cookie to store the jwt a good idea? Or did I completely misunderstood the idea of JWT and it is not used to be used for a login?
Additionally: I still don't know if biulding the login as an API was the best idea.
First of all there is nothing wrong with building "login with API". It is common practice. And JWT is perfectly suited for auth.
You sure can store JWT token inside a cookie, but it is a little bit wrong in my opinion. Usually JWT tokens are stored in the local storage on the client side. It will persist after page reload.
Set token in the local storage:
localStorage.setItem('token', token);
Get token from the local storage:
token = localStorage.getItem('token');
To better understand the conception of the JWT you can copy some token (without Bearer) and paste it in jwt.io. Basically JWT contain all the required information about the user and server can trust this information.
And when you set the cookie like this
set_cookie([
'name' => 'login',
'value' => $token,
'expire' => $exp,
'httponly' => true,
'secure' => true,
]);
It is an overkill. JWT possible already contains all this information and you can extract it from the token.
I am developing a widget that users in my company can use to communicate with end-users through Smooch.
The widget is accessible through the web browser and the communication goes mostly through a layer developed in node. However, I was trying to send attachments directly to Smooch to reduce the load in the server.
As I understand, it is necessary to use a token with a appUser scope to avoid issues with CORS.
I create the token using the following code
app.get('/getjwt', (req, res) => {
var token = jwt.sign({ scope: 'appUser', userId: req.body.userId }, SECRET, { header: { 'alg': 'HS256', 'type': 'JWT', 'kid': '[app key ID]' } });
res.send({ jwt: token });
});
I try to use the generated token (using Postman for tests) by making a request with Authorization Bearer [my generated token] and I get the following error:
{
"error": {
"code": "invalid_auth",
"description": "Invalid JWT header. Missing key id (kid)"
}
}
I have tried changing the 'kid' value to the app ID, the API key ID, and the API key Secret and I'm always getting the same error. What am I missing? Am I supposed to pass the Key ID somewhere else?
Thank you,
Your code works fine for me, what version of jsonwebtoken are you using? In v6.0.0 the headers option was renamed to header, so if you're using 5.x or lower your code should look like this instead
var token = jwt.sign({ scope: 'appUser', userId: req.body.userId }, SECRET, { headers: { 'alg': 'HS256', 'type': 'JWT', 'kid': '[app key ID]' } });
That said, Smooch already provides a fully functional web messenger / widget that you should use instead of attempting to build your own. It provides event hooks and methods to build a fully custom UI if that's what you're trying to achieve. See https://docs.smooch.io/guide/web-messenger/ and https://www.npmjs.com/package/smooch
I barely started reading about JWT and I beliave I understand what a JWT token is. I am also fairly familiar with SESSIONS. And I believe I understand the pros of each as well as their cons. However, there are a couple of parts where I am confused.
When requesting a protected resource, you need to send the jwt on each request, as opposed to having a session stored on the server. But:
1) how do you store your JWT token and where. From what I read I understood that you send your request to authenticate to the server and the server sends you a JWT token if you are successfully authenticated. Then what do you do?, do you store the JWT in a cookie as I have read in some sites? If so, how do you do it (using php, using javascript). And how do you read it.
2) When using session, more or less you just check that there is a session to check the user is logged in. How do you accomplish this when using JWT.
Also I have seen this on some pages:
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
How is this related to this (if related at all)
From client side, the good practice is store JWT in cookie, with mode http_only=true, is_secure (so that only send through https), so that JWT is not accessible by javascript. Then, we don't worry about XSS attach.
We dont need to store the session on server side. A JWT contains two parts, the payload data, and signature, signed by a secret key stored on server side, and only the server could know. When we receive the token from client, we check the payload data is valid or not (user information, who assigned that token, assigned that token to whom, which roles granted with the token, expired time), and we check the signature to make sure that the token is assigned by the server, not faked. Then the user will be authenticated.
It's like a passport the government give to its citizen, the data (payload) is readable for everybody, but the signature can only created by the government, and it can verify against that.
JWT is mostly a way to authenticate a user on rest APIs as, like you said, you send it to the client, and it negates the need to store it in a session.
however, if you are making a browser application, i don't see the need to use JWT authentication, as you can use the session and cookies to do so.
JWT is mainly for those cases, when you have a, say, mobile application frontend, where maintaining a session is not suggested, and maybe impossible.
however, if you are making a hybrid application, then storing it in local storage of the "browser" is the way to go. the JWT is NEVER stored server side.
In my case I use use Illuminate\Foundation\Auth\AuthenticatesUsers;, use JWTAuth; and use Tymon\JWTAuth\Exceptions\JWTException; in my LoginController. Then I need to get my JWTToken like that
use AuthenticatesUsers;
try {
$token = JWTAuth::attempt($request->only('username', 'password'), [
'exp' => Carbon::now()->addWeek()->timestamp,
]);
} catch (JWTException $e) {
return response()->json([
'error' => 'Could not authenticate: ' . $e.message,
], 500);
}
Of cause I do somthing more, if I don't get a token. In my case I go like that:
if (!$token) {
return response()->json([
'error' => 'Could not authenticate.'
], 401);
} else {
$data = [];
$meta = [];
//all what i need from users table if auth
$data['id'] = $request->user()->id;
$data['email'] = $request->user()->email;
$meta['token'] = $token;
//now comes the part, where I set my sessions:
Session::put('auth-user', (array)$request->user());
Session::put('jwt-token', $token);
Session::save();
return response()->json([
'data' => $data,
'meta' => $meta
])->withCookie('jwt-token', $token, config('jwt.ttl'), '/', null, true, true);
}
This is actually that what I do in LoginController I also have a middleware where I have a function to handle stuff like behavior after refresh page an so on.
'my.auth' => \App\Http\Middleware\MyAuth::class
I also handle a lot in javaScript's localStorage and sessionStorage in vuex store. To define routes in web.php or api.php I'll use now the class what I defined in
middleware before Route::group(['middleware' => 'my.auth'], function(){...}
Some resources that helped me out:
LaravelCookies, LaravelSession
Hope you get a bit inspiration to create your JWT auth.
I'm having a Single Page Application written in AngularJs (The framework is irrelevant at this point) The application is hosted in IIS and it's compose of index.html plus a bunch of client assets.
On backend I have WebApi 2, hosted also in IIS as a separate application.
For authentication on client I'm using Firebase (simple login) with several social netoworks enabled, like Facebook, Twitter or Google.
So far so good. I like how easy it is to enable twitter authentication for example with firebase.
On login with social network i get back from firebase, the firebaseAuthToken and provider accesstoken.
Now I want to use firebaseAuthToken or provider access token to authenticate with my WebApi.
The question is: What is the best way to authenticate with WebApi in given conditions?
There is not an option to use only firebase to store my data and get rid of web api since I have in place complex business logic on server.
One silly idea that i have so far, is to pass the social provider access token to the server, validate the token against provider and then issue a security token using Owin -Katana.
I'm not using build in social providers support from katana due to lack of documentation, complexity and bad integration with single page apps. I found the visual studio template for SPA too mvc specific. But that's me :)
tl;dr - Demo Project on GitHub
The steps below may seem long, but it's actually really easy. I created my demo project in just an hour or so.
I agree with you about using Owin and Katana. I've been through that process before and it wasn't really a great experience. Using Firebase was a heck of a lot easier.
This can all be done with JWTs!
When you authenticate through Firebase and whatever social provider, you get back a JSON Web Token (JWT) - firebaseAuthToken.
Grab your Firebase Secret from the Dashboard
The way JWTs work is that we have a secret token and a client token. The client token is the firebaseAuthToken we receive after logging in. The secret token is generated for us in the Firebase Dashboard.
Store your Firebase Secret in the appSettings section of your Web.config
We need to store this secret key in the Web.config so it's easier to access later.
<add key="FirebaseSecret" value="<Firebase-Secret-Token-Goes-Here" />
Create an Action Filter to check the JWT from the Authorization Header
We can verify the request is valid by passing the client token in the Authorization header. On the server we can store our secret key that we get from our Firebase Dashboard. When the request is checked by Web API we can decode the JWT using a JWT Library (available from NuGet). If the decoding is successful then we can check to token to make sure it isn't expired.
public class DecodeJWT: ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
string firebaseAuthToken = string.Empty;
if (actionContext.Request.Headers.Authorization != null) {
firebaseAuthToken = actionContext.Request.Headers.Authorization.Scheme;
} else {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
}
string secretKey = WebConfigurationManager.AppSettings["FirebaseSecret"];
try {
string jsonPayload = JWT.JsonWebToken.Decode(firebaseAuthToken, secretKey);
DecodedToken decodedToken = JsonConvert.DeserializeObject < DecodedToken > (jsonPayload);
// TODO: Check expiry of decoded token
} catch (JWT.SignatureVerificationException jwtEx) {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
} catch (Exception ex) {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
}
base.OnActionExecuting(actionContext);
}
}
Create a $httpInterceptor add the firebaseAuthToken to the header for every request
On the client, the trick is that the token has to be passed every time. To make this easier we need to create a $httpInterceptor with Angular that checks for a firebaseAuthToken on sessionStorage.
.factory('authInterceptor', function ($rootScope, $q, $window) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.firebaseAuthToken) {
config.headers.Authorization = $window.sessionStorage.firebaseAuthToken;
}
return config;
},
response: function (response) {
if (response.status === 401) {
// TODO: User is not authed
}
return response || $q.when(response);
}
};
})
Set the firebaseAuthToken to sessionStorage on a successful login
Whenever a user logs in we can set the value to sessionStorage.
$rootScope.$on('$firebaseSimpleLogin:login',
function (e, user) {
// add a cookie for the auth token
if (user) {
$window.sessionStorage.firebaseAuthToken = user.firebaseAuthToken;
}
cb(e, user);
});
Register the DecodeJWT filter globally
Inside of the WebApiConfig.cs Register method we can set the DecodeJWT filter to apply for all of our ApiControllers.
config.Filters.Add(new DecodeJWT());
Now whenever we make a request to an ApiController it will reject it unless there is a valid JWT. So after a user logs in we can save their data to a ApiController if it already doesn't exist.
// globally uses DecodeJWT
public class UsersController: ApiController
{
// POST api/users
public void Post([FromBody] FbUser user) // See GitHub for this Model
{
// Save user if we do not already have it
}
}