Authentication¶
How to login as a Frontend-User using axios and send requests to the TYPO3 Rest Api¶
In most cases you will want to restrict access to your TYPO3 Rest Api to certain users or usergroups. The basic way to do this in your classes and methods, is to use the @ApiAccess() Annotation.
The nnrestapi-extension comes with a default endpoint to authenticate as a Frontend User using the
credentials set in the standard fe_user
-record.
To keep the frontend user logged in, TYPO3 usually sets a cookie. Cookies tend to get rather ugly when you are sending cross-domain requests, e.g. from your Single Page Application (SPA) or from a localhost environment.
The nnrestapi solves this by also allowing authentication via JWT (Json Web Token).
Let’s have a look, how to authenticate, retrieve a JWT with axios and pass it to the server when making follow-up request to your TYPO3 Rest Api.
Tip
Authentication with axios¶
Use a simple POST
-request to the endpoint /api/auth
and pass your credentials wrapped in a JSON to
authenticate as a TYPO3 Frontend-User. If you were successfully logged in, you will get an array with
information about the frontend-user and the JSON Web Token (JWT).
In the following script we are simply “memorizing” the JWT by storing it in the localStorage for later requests.
// This endpoint is part of the nnrestapi
const authUrl = 'https://www.mywebsite.com/api/auth';
const credentials = {
username: 'john',
password: 'xxxx'
};
axios.post(authUrl, credentials).then(({data}) => {
alert( `Welcome ${data.username}!` );
localStorage.setItem('token', data.token);
}).catch(({response}) => {
alert( `Error ${response.status}: ${response.data.error}` );
});
If you were john
and we guessed your password right, the response of the above example will look something like this:
{
uid: 9,
username: "john",
usergroup: [3, 5],
first_name: "John",
last_name: "Malone",
lastlogin: 1639749538,
token: "some_damn_long_token"
}
The most important part of the response is the token
. You will need to store the value of the token in a variable
or localStorage like we did in the example above.
Sending authenticated requests¶
After you retrieved your JSON Web Token (JWT) you can compose requests with the Authentication Bearer
header.
Let’s send a request to an endpoint that has an restricted access and only allows requests from fe_users
.
This can be done, by setting @Api\Access("fe_users")
as Annotation in the endpoints method.
// Your endpoint. Only fe_users may access it.
const url = 'https://www.mywebsite.com/api/test/something';
// The JWT we stored above after authenticating
const token = localStorage.getItem('token');
const xhrOptions = {
withCredentials: true,
headers: {
Authorization: `Bearer ${token}`
}
};
axios.get( url, xhrOptions ).then(({data}) => {
console.log( data );
}).catch(({response}) => {
alert( `Error ${response.status}: ${response.data.error}` );
});
Setting the credentials globally for future requests in axios¶
Nice feature in axios: You can store the credentials / Bearer token globally for every follow-up request
you make with axios. So, after successfully authenticating, you could have all subsequential calls to
the Api automatically send the Authentication Bearer
:
// This endpoint is part of the nnrestapi
const authUrl = 'https://www.mysite.com/api/auth';
// This is your endpoint that is only accessible by frontend-users
const restrictedUrl = 'https://www.mysite.com/api/some/endpoint';
const credentials = {
username: 'john',
password: 'xxxx'
};
// Authenticate frontend user
axios.post(authUrl, credentials).then(({data}) => {
// set default headers for all future requests
axios.defaults.withCredentials = true;
axios.defaults.headers.common.Authorization = `Bearer ${data.token}`;
// now send test request
sendTestRequest();
}).catch(({response}) => {
alert( `Error ${response.status}: ${response.data.error}` );
});
// test to endpoint that requires authentication
function sendTestRequest() {
// Authorization headers are automatically sent now!
axios.get( restrictedUrl ).then((result) => {
console.log( result );
}).catch(({response}) => {
alert( `Error ${response.status}: ${response.data.error}` );
});
}
Checking the login status¶
The nnrestapi comes with an endpoint to check, if the JWT is still valid. Or, another words, If the frontend-user is still logged in and has a valid session.
Hint
The session lifetime (the time the frontend-user session is valid) can be set in the backend.
Have a look at the extension configuration for nnrestapi
in the Extension Manager.
Simply send a GET
-request to the endpoint /api/user
and pass the Authentication Bearer
header.
If the session is stil valid, the API will return information about the current frontend-user.
// This endpoint is part of the nnrestapi
const checkUserUrl = 'https://www.mywebsite.com/api/user';
// The JWT we stored above after authenticating.
const token = localStorage.getItem('token');
// Not needed if you used axios.defaults.headers.common, see above
const xhrOptions = {
withCredentials: true,
headers: {
Authorization: `Bearer ${token}`
}
};
axios.get( checkUserUrl, xhrOptions ).then(({data}) => {
console.log( data );
}).catch(({response}) => {
alert( `Error ${response.status}: ${response.data.error}` );
});
The result will be very similar to the object returned during authentication, but the response
will not contain the token
:
{
uid: 9,
username: "john",
usergroup: [3, 5],
first_name: "John",
last_name: "Malone",
lastlogin: 1639749538
}
Full axios “Starter Template” with login-form¶
Here is a template with login-form and testbed to get you started. It will show a login-form and - after successful authentication -
a testform to send JSON-requests with GET
, POST
, PUT
, DELETE
and PATCH
requests to your TYPO3 Restful Api.
Tip
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>nnrestapi axios Demo with authentication</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<style>
#json-data {
min-height: 100px;
}
#result {
min-height: 100px;
white-space: pre-wrap;
border: 1px dashed #aaa;
background: #eee;
padding: 0.75rem;
}
</style>
</head>
<body>
<div class="container my-5" id="login-form">
<div class="form-floating mb-4">
<input class="form-control" id="url-auth" value="https://www.mysite.com/api/auth" />
<label for="url-auth">URL to auth-endpoint</label>
</div>
<div class="form-floating mb-4">
<input class="form-control" id="username" value="" />
<label for="username">Username</label>
</div>
<div class="form-floating mb-4">
<input type="password" class="form-control" id="password" value="" />
<label for="password">password</label>
</div>
<div class="form-floating mb-4">
<button id="btn-login" class="btn btn-primary">Login</button>
</div>
</div>
<div class="container my-5 d-none" id="test-form">
<div class="form-floating mb-4">
<select class="form-select" id="request-method">
<option>GET</option>
<option>POST</option>
<option>PUT</option>
<option>PATCH</option>
<option>DELETE</option>
</select>
<label for="request-method">Request method</label>
</div>
<div class="form-floating mb-4">
<input class="form-control" id="url-request" value="https://www.mysite.com/api/user" />
<label for="url">URL to endpoint</label>
</div>
<div class="form-floating mb-4">
<textarea class="form-control" id="json-data">{"title":"Test"}</textarea>
<label for="json-data">JSON data</label>
</div>
<div class="form-floating mb-4">
<button id="btn-request" class="btn btn-primary">Send to API</button>
</div>
<pre id="result"></pre>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* Login form
*
*/
document.getElementById('btn-login').addEventListener('click', () => {
const authUrl = document.getElementById('url-auth').value;
const credentials = {
username: document.getElementById('username').value,
password: document.getElementById('password').value
};
// Authenticate frontend user
axios.post(authUrl, credentials).then(({data}) => {
// set default headers for all future requests
axios.defaults.withCredentials = true;
axios.defaults.headers.common.Authorization = `Bearer ${data.token}`;
document.getElementById('login-form').classList.add('d-none');
document.getElementById('test-form').classList.remove('d-none');
}).catch(({response}) => {
alert( `Error ${response.status}: ${response.data.error}` );
});
});
/**
* Test form
*
*/
document.getElementById('btn-request').addEventListener('click', () => {
const requestUrl = document.getElementById('url-request').value;
axios({
url: requestUrl,
method: document.getElementById('request-method').value,
data: document.getElementById('json-data').value
}).then(({data}) => {
document.getElementById('result').innerText = JSON.stringify( data );
}).catch(({response}) => {
alert( `Error ${response.status}: ${response.data.error}` );
});
});
</script>
</body>
</html>