User registration for API/SPA - laravel

I am creating an API and a separate front-end app that will consume said API. In my particular case I'm using Laravel Passport for my API and some VueJS for my frontend app.
In order for a user to create an account, a user must POST to a route (/oauth/token) on the API which, requires a client_secret to be passed (https://laravel.com/docs/5.3/passport#password-grant-tokens).
The only options I see are:
Having the client_secret sent as a header from my frontend app. However, putting this token out in the open doesn't seem smart.
Don't require the client_secret at all. This doesn't seem much better than option 1.
Have a dynamic page on my frontend app that can securely store the client_secret and then send it to the API. While this is obviously the most secure, it seems to partially defeat the purpose of a fully static frontend (SPA).
What's the best practice for this type of approach? I've searched for how this is dealt with in general with an API and SPA, but I haven't found anything that points me in the right direction.

From my point of view, the Laravel Passport component seems to implement the OAuth2 Framework Protocol incorrectly.
The client_id and client_secret parameters are not part of the grant type.
For the Resource Owner Password Credentials grant type, the required parameters are username and password (see RFC6749 section 4.3.2).
client_id and client_secret are used to authenticate a confidential client that sends its credentials through the body parameters (see RFC6749 section 2.3.1). The Laravel Passport component should allow other client authentication schemes (especially the HTTP Basic Authentication Scheme). The RFC6749 also indicates that
Including the client credentials in the request-body using the two
parameters is NOT RECOMMENDED and SHOULD be limited to clients unable
to directly utilize the HTTP Basic authentication scheme
The OpenID Connect Core specification lists some of those schemes in its section 9. The RFC6749 does not indicates how public clients (e.g. SPA) should authenticate against the token endpoint. They are supposed to use the Implicit grant type which does not require a client authentication.
Anyway, a solution could be to use a kind of proxy. This proxy has to be installed on a server. It will receive all requests from the SPA (without client secret), add the client secret and transmit the modified request to the Laravel Passport endpoint. Then the response is sent to the SPA. This way the SPA never exposes the client secret.

I came across the same problem, and I didn't find much more documentation on the problem.
So here is what I did, that seems working great so far, you'll tell me if you see anything wrong.
For my apps, I'll be using password grant clients that I create on the fly for each "client" of my app. By client I mean browser, or mobile app, or anything.
Each browser, checks at startup if they have any client_id and client_secret into localStorage (or cookies, or anything). Then, if they don't, they call an endpoint of your API that will create a password grant client and return the information to the browser.
The browser will then be able to login the user using this new client information and his credentials.
Here is the controller I use to create a password grant client:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Http\Request;
use Laravel\Passport\ClientRepository;
class AuthController extends Controller
{
protected $hasher;
protected $clients;
public function __construct (Hasher $hasher, ClientRepository $clients)
{
$this->hasher = $hasher;
$this->clients = $clients;
}
public function makeClient (Request $request)
{
$client = $this->clients->create(null,$request->header('User-Agent','Unknown Device'), '', false, true);
return $client->makeVisible('secret');
}
}
As you can see, as the name for the client, I try to store the User-Agent of the browser. So I can potentially display a page to my user with all his clients and giving him the right to revoke some clients like:
"Google Chrome, New York". You can also store the client IP or anything in there that will help you identify more precisely the client type of device...

The simpler way would be to take care of the user registration with the Laravel app running Passport itself (and not with the frontend Vuejs app via API).
Once the user is registered and logged in, you can use Passport's CreateFreshApiToken middleware to add a token to the user's cookie while loading up your frontend app. No more problem with client_secret.
See https://laravel.com/docs/5.3/passport#consuming-your-api-with-javascript and https://mattstauffer.co/blog/introducing-laravel-passport#super-powered-access-to-the-api-for-frontend-views
Also oauth/token doesn't create a user I believe? It is supposed to deliver a token (for password grant client) or an authorization code (authorization code grant client).

Related

How to prevent exposing client secret when using laravel passport?

I'm trying to implement laravel's passport to protect my api routes and I have a case where the route should be inaccessible unless it is called by an authorized application. I am trying to use Client Credentials Grant Tokens and using postman I am able to generate an access token, which then I can use for access authorization.
The problem is - I don't understand how should I safely use this with Vue and axios. I have my component in which I need to call this api, I can of course set a form body including all the necessary fields (client_id, client_secret and grant_type) but that would mean that anyone could just open up chrome dev tools and search for client_secret in the source and they would get the hardcoded client secret, which would grant them access to the api. What is the right way to do this?
It depends on how you use your Vue frontend.
If it is a frontend mostly for your own site, but sometimes needs to access an external API, than you should have your backend make the API calls and store secrets there.
If you are developing a Vue frontend dedicated to the external API, but running on a different domain, you could go for the PKCE option: https://laravel.com/docs/8.x/passport#code-grant-pkce
If you have a frontend on the same domain as the API, use the CreateFreshApiToken option provided by Laravel passport.

Issue understanding Laravel 6.0 Passport with Password Grant Tokens flow

I'm having issues understanding the whole process of authenticating a client to consume my API built on Laravel. Some things just don't click for me right now.
I'm trying to implement an API and an OAuth server both on Laravel. The API will be consumed by a native mobile app that is trusted. The flow that makes more sense to me is "Password grand token" as described in the Laravel's Passport docs: https://laravel.com/docs/7.x/passport#password-grant-tokens
As i understand the implementation:
User installs my mobile app.
Upon installation, he's prompted with the "enter username/password" to continue to use the app
Upon hitting submit, i make a POST request to my Laravel oAuth server implementation on "/oauth/token" with "grant_type", "client_id", "username", "password", "scope". I'm leaving out the "client_secret" because i understand that it's not a good idea to store the secret on the client device.
The server then checks the already created( `php artisan passport:client --password` ) "client_id", "username", "password" and "response_type"
If all matches, it generates a token, and responds with "acces_token" & "refresh_token"
I can make now make calls to my API's endpoints "/api/whatever/method_name"
My issue is at point 4. I can only issue the access token if the user already exists in my database, but i'm assuming it's the first time the user uses my app. postman_response
Do i also need an "authentification" step, in witch the user sends username/password and the OAuth server prompts the "authorize app" to use your data, and at this point to save the user in the database and only then proceed?
Usually you have an register route, that is without authorization else you have no entry into the application. Imagine your routes file.
Route::middleware('auth:api')->group(function () {
Route::post('/todos', 'TodoController#index');
});
// Without auth
Route::post('/register', 'RegisterController#register');
For hiding credentials, it is often easier to do a proxy approach, so you backend can hold client_id and client_secret, since it will always be the same (unless you are building an oauth server).
Route::post('/login', 'LoginController#login');
Which will receive username and password, internally call oauth/token and add client_id and client_secret in the process and return the token. To save some calls through the signup, you can do the same approach after you have registered, get the oauth token, with the credentials you have at registrering and return the token imediatly.
I would recommend the following:
In log in method, check if user exists.
If exists, do log him in.
else, first register him up, and then log him in
lastly, return access token

Password Grant access token generation

I'm using Laravel Passport password grant type to enable my mobile clients(Android, iOS ...) to generate an access token. My mobile clients cannot use oauth/token route to get access and refresh tokens themselves, because it uses web middleware (as I understand). My questions is,
Should I make request to oauth/token myself in server by passing data mobile clients posted? If I do this how do I bypass web middleware. (Manually creating a dummy user and using it to bypass web middleware is not that I want and weird solution)
In my particular case I always this by removing \Illuminate\Session\Middleware\AuthenticateSession::class from the web middlewareGroup on app/Kernel.php.
I'm also using laravel-cors to allow Cross-Origin Resource Sharing headers. My apps are API only and the clients are external (both mobile and web clients are external) and they all authenticate and renew tokens themselves interacting with the Passport routes.

Laravel API Auth with Passport and React

I have a Laravel 5.5 Application that's using the session based auth out of the box. On some of these pages I have react components that need to get/post data from/to an API.
What is the best practice for handling this? Do I simply hide the API endpoints behind the auth? This would work but should I be using Laravel Passport for this instead?
I've had a play with Passport and it seems that this would work but I don't need users to be able to create clients and grant 3rd party applications permission etc. There is just the first party react app consuming the data from inside the laravel application (view).
From my initial experimenting with it, it seems I'd need to have the login call made first to receive an access token to then make further calls. As the user will already be authenticated in the session is there an easier way?
I'm not sure if Passport is intended to be used for this purpose or not. I'd rather take the time to get it right now as I'd like to get the foundations right now if the app scales.
You can proxy authentication with Passport. Using the password grant type users would still log in with their username/password, then behind the scenes make an internal request to Passport to obtain an access token.
Restrict what routes are available when registering in a service provider by passing in:
Passport::routes(function ($router) {
$router->forAccessTokens();
$router->forTransientTokens();
});
That limits access to personal tokens and refresh tokens only. A client will be created when you run php artisan passport:install.
Setup a middleware to merge the password grant client id and secret in with the request, then make a call to the authorization endpoint. Then it's just a matter of returning the encrypted token and observing the Authorization header for requests to your api.

Laravel Passport APi - Implicit grant

I want to build a spa via angularjs and use laravel as a api for the spa. Reading trough the docs of laravel passport i discovered that i need to use the implicit grant for this purpose. But i am not really sure in how it should work from front to back. I just want to have the ability to log in a user with a username and password and then just use it and i need some clarification on the process. This is what i want:
Log in with a user by a username and password via html/javascript to laravel (Angular) via an ajax request.
Get an access token to communicate with the api
Do some action in the spa that triggers a request to the api using the access token
Getting data back from the api in response to that request.
But what i see now with the implicit grant i a bit different than what i expect.
Log in to laravel via a default blade login form (did not create one using ajax yet)
Redirect to oauth/autorize like this
Route::get('/redirect', function () {
$query = http_build_query([
'client_id' => 'client-id',
'response_type' => 'token',
'scope' => '',
]);
return redirect('http://your-app.com/oauth/authorize?'.$query);
});
The redirect shows an approve or deny authorization request screen (this is not what i expect)
When i approve the request, the browser redirects me to the redirect uri that is specified in the oAuth client database entry with the access token. And i should be able to.
What confuses me even more is the fact that i seem to need a new client for each laravel user. I expect to have 1 oauth client representing my spa that can access the laravel users. Could you please clarify this?
If you are going to use a password grant in a JavaScript application then you must use a server side proxy to do the authentication and secure both client_secret and the refresh token.
The proxy manages the whole api communication process or just the authentication part and returns a short lived access_token . Authentication state is managed via a server session. Some requests must be protected from CSRF exploits depending on your implementation since most implementations use a cookie.
Otherwise use an implicit grant to authenticate your app. (See links below for more info)
https://auth0.com/docs/api-auth/tutorials/implicit-grant
https://oauth2.thephpleague.com/authorization-server/implicit-grant/
You can refresh your access token using silent authentication as described here
https://auth0.com/docs/api-auth/tutorials/silent-authentication
NB: In most cases refresh tokens do not expire, that's a big NO for Frontend storage.
Client Secret should always be kept secret.
Edit (2020)
It's now 2020 and a lot has changed in the web security world.
There are known vulnerabilities with implicit grant especially since your access_token can be intercepted mid-flight and redirected to another server.
It's now recommended to use PKCE flow instead of implicit grant
Okta has a very nice article and video regarding this Is the OAuth 2.0 Implicit Flow Dead?
Laravel has also released a much simpler alternative Laravel Sanctum I suggest you have a look at it as it uses secure HTTP only cookies for access token storage and also implements CSRF protection out of the box

Resources