I used firebase authentication to secure my ASP.NET CORE api.
I actually store the users in the database my API uses. Note that in my database the google identifiers are the uids generated by the firebase authentication and that the classic identifiers (login + password) are generated in my API.
When the user connects with Google, the token is created in the client(Angular) so I send it to my API (of course I don't store it), I just check if the token is valid and if the id which is contained in the token corresponds to the identifier of one of the users which is stored in my database.
In my client, for google authentification:
async GoogleAuth() {
try {
return new Promise((resolve, reject) => {
signInWithPopup(this.auth, this.provider).then(() => {
this.auth.onAuthStateChanged((user) => {
if (user) {
user
.getIdToken()
.then((idToken) => {
this.sendTokenUserGoogleToAPI(user, idToken)
.then((data: any) => {
localStorage.setItem('token', data.token);
resolve(data);
})
.catch((error) => {
console.log("googleAuth : " + error)
reject(error);
});
})
.catch(() => { });
}
});
});
});
} catch (e) { }
}
For the classic connection (login + password), the data is sent directly to my API and I create a personalized token with the user ID in my backend and I send the token to the client who generates a personalized token to from the token.
In my API, for classic authentication :
[HttpPost]
[ActionName("signin")]
public async Task<ActionResult> SignIn([FromBody] UserLoginViewModel userModel)
{
var user = await _context.Users.FirstOrDefaultAsync(u =>
u.Login == userModel.Login && u.Password == userModel.Password);
if (user == null)
{
_logger.LogWarning("Connection attempt failed.");
return NotFound(new { message = "User or password invalid." });
}
if (user.IsLocked)
{
return new ObjectResult(new { message = "Your account has been blocked." }) { StatusCode = 403 };
}
var token = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(user.UserId);
var login = user.Login;
return Ok(new
{
login,
token
});
}
In my client, when I receive the token from my API
signInWithCustomToken(getAuth(),token)
.then((userCredential) => {
const user = userCredential.user;
console.log(token)
user!.getIdToken(true).then((idToken) => {
localStorage.setItem('token', idToken)
}).catch((error) => {
console.log(error)
})
})
So I'm guessing I shouldn't generate the token in my backend and only return the id to generate the token in the client? I regenerate the token in my client so that the user can access the chat
With my client, is it better that I get the token by querying firebase each time or is it better that I store this token locally to be able to use it in my requests?
For the moment, I store it locally but I think that it can be problematic if the token changes or if an attacker modifies his token because I verify thanks to firebase that the user is connected, if the local token changes, firebase will always say that the user is logged in but in my api the token will not be valid.
The ID token you get from Firebase Authentication is an exp property/claim that shows you until when it's valid. Firebase's own SDKs refresh the token about 5 minutes before it expires, so your code should probably do the same. In fact, if you listen for when the ID token changes (Android web), you don't have to force a refresh yourself and can just piggyback on the work the SDK already does for you.
Related
I am trying to create a product verification system, and I have the login part down. My question is how are there any API (s) that can verify something like an activation code and return if it succeeded or not.
Btw, you might have to scroll horizontally to see all of the code
//How would I add a verification system
document.getElementById('redeemButton').addEventListener('click', () => {
var code = document.getElementById('redeemCodeBox').value;
var product = document.getElementById('productCode').value;
const fetchPromise = fetch(`https://www.mywebsite.com/api/redeem?product=${product}&code=${code}`);
fetchPromise.then( response => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
})
.then( json => {
console.log(json[0].name);
})
.catch( error => {
console.error(`Could not get products: ${error}`);
throw `Please Enter a Valid Activation Code`;
});
});
I don't think you should be dependent on a third party to verify an Activation Code.
You should use a combination of JWT token + activation code that you store in the database.
Generate an activation code.
Generate JWT token.
const token = await this.jwtService.sign(
{
usetId: userId
},
{ expiresIn: "1d" }
);
Save the activation code and the JWT token to the database.
Send an E-mail or SMS with an activation code to the user. + include URL where user can insert the activation code.
URL can look something like this:
https://www.mywebsite.com/api/activation?token=eyJhbGciOiJIfasdfas
(use query parameter for JWT token =>> ?token=JWT_token
Verify JWT token from URL (value from query parameter "token") and validate if Token is not expired.
Control if user input matches an Activation code stored in your database.
Activated
I am working with the Firebase Admin SDK for Nodejs so that I can mint custom tokens for authentication in iOS devices. In the Nodejs docs it states that you send the token back to the client after it is created.
let uid = 'some-uid';
admin.auth().createCustomToken(uid)
.then(function(customToken) {
// Send token back to client
})
.catch(function(error) {
console.log('Error creating custom token:', error);
});
My question is the most efficient way to do this. I've been thinking about creating Could Function to send it back in a response body but I feel like I may be over thinking it. Is this the recommended method or is there a simpler way I am missing?
Here is the working code from my application (cloud function) that is as simple as copy paste for your reference.
exports.getCustomToken = functions.https.onRequest(async (req, res) => {
return cors(req, res, async () => {
try {
const token = await createCustomToken(req.body.uid);
return res.json(token);
} catch (error) {
res.status(500).json({ message: 'Something went wrong' });
}
});
});
async function createCustomToken(userUid, role = '') {
let createdCustomToken = '';
console.log('Ceating a custom token for user uid', userUid);
await firebaseAdmin.auth().createCustomToken(userUid)
.then(function (customToken) {
// Send token back to client
console.log('customToken is ', customToken)
createdCustomToken = customToken;
})
.catch(function (error) {
console.log('Error creating custom token:', error);
});
return createdCustomToken;
}
I wouldn't worry too much about efficiency at this point. The token is small and is quick to generate. The first thing to do is just make it work. Use Cloud Functions if you prefer, but the Admin SDK will work on any modern nodejs backend.
I like to understand the JWT handling of token.
I have created a login page to check if user exist in DB? If yes, I used jwt sign a token and return jwt token.
jwt.sign({userdata}, secretKey, (err, token) => {
res.json({
token
After I get the token I understand I have store it in local storage.
localStorage.setItem("token", token);
After this I am lost! How can I redirect the login to a protected URL once the token is stored?
Then my next question is how can I make use of the local stored token in the protected route?
For example login.html will invoke a login function call and return the token then I want to go to /admin/admin.html. In /admin/admin.html, i have protected routes that need to use the token. How can I use it ? How can I know the user is the same user using the protected route since? I know the localstored token has the user information. Does that mean every protected route I have to post a user information and compare to local token?
Some examples of the code will be useful. Thanks
You can do something like that
login() {
try{
const tk = response.token; // from api response
if (tk) {
const expiresInDuration = response.expiresIn;
setAuthTimer(expiresInDuration); // setTimer to not send rest call everytime if user is visiting many times
const now = new Date();
const expirationDate = new Date(
now.getTime() + expiresInDuration * 1000
);
this.saveAuthData(this.token, expirationDate, role);
navigate(['/home']); // function which should redirect to your desired url
}
}, (err) => {
console.log(err)
});
}
// function to auto logout after specified time
setAuthTimer(duration: number) {
this.tokenTimer = setTimeout(() => {
this.logout();
}, duration * 1000);
}
saveAuthData(token, expirationDate, role) {
localStorage.setItem('token', token);
localStorage.setItem('expiration', expirationDate.toISOString());
}
// after delete and log out
clearAuthData() {
localStorage.removeItem('token');
localStorage.removeItem('expiration');
}
// function to login user if its data is already present in the localStorage
autoAuthUser() {
authInformation = getAuthData();
if (authInformation) {
const now = new Date();
const expiresIn = authInformation.expirationDate.getTime() - now.getTime();
if (expiresIn > 0) {
this.token = authInformation.token;
this.isAuthenticated = true;
this.setAuthTimer(expiresIn / 1000);
}
}
}
For your question regarding same user is accessing the protected route as local storage is storing token specific to user that should take care of the task
You have to use a library that verifies your stored JWT token. You can use https://www.npmjs.com/package/jsonwebtoken . This library includes a method that verifies your JWT jwt.verify(token, secretOrPublicKey, [options, callback]). To be able to verify a token, you must provide the secret key that is used to sign your tokens. If the token is verified successfully, you can redirect the user to its designated page. As long as the token is stored and not expired, the user is remembered in the browser.
This is an approach for JS apps, however, if you're using PHP/Laravel, the token is stored in a HTTP cookie and I recommend using jwt-auth library, it will handle the JWT processes for you.
I'm requesting a user's info via Microsoft Graph. I use the 2.0 endpoint.
This is my login function:
login() {
hello('msft').login({scope: Configs.scope}).then(
() => {
this.zone.run(() => {
this.meService.getMe().subscribe(data => {
localStorage.setItem('username', data.mail);
localStorage.setItem('jobtitle', data.jobTitle);
localStorage.setItem('loggedin', 'yes');
},
err => {
console.log(err);
},
() => {
this.router.navigate(['/home']);
});
});
},
e => console.error(e.error.message)
);
}
This is my init function:
initAuth() {
this.redirect_uri = window.location.href;
hello.init({
msft: {
id: Configs.appId,
oauth: {
version: 2,
auth: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
},
scope_delim: ' ',
form: false
},
},
{redirect_uri: window.location.href}
);
}
And here I am getting the access token:
getAccessToken() {
const msft = hello('msft').getAuthResponse();
console.log(msft);
const accessToken = msft.access_token;
return accessToken;
}
I get an access token, via which I can login. However, I get no refresh token. From what I read, you get the refresh and the access token via the /token endpoint. As far as I can see, I only use the /authorize endpoint and it works?
This poses a problem. I can't refresh my token!
A response looks like this:
access_token:
"This is private, but it's a very long string"
client_id:"e6c987d2-8bdc-4f1a-bafc-04ba3d51f340"
display:"popup"
expires:1524649746.548
expires_in:3599
network:"msft"
redirect_uri:"http://localhost:4200/"
scope:"basic,User.Read"
session_state:"89a68bd2-5ae5-4df2-88d0-d28718fd10bc"
state:""
token_type:"Bearer"
Any help would be appreciated!
Since you're using the Implicit grant, you cannot use Refresh Tokens. They're only supported using the Authorization Code grant.
In order to use Refresh Tokens, you'll need to switch to the Authorization Code grant and implement the server-side code to process the authorization code into an access token. You'll also need to request the scope offline_access which triggers the generation of a refresh_token.
How to create middleware which will catch all errors, for example I have request which required token, token can expired or damaged, so I need catch this errors on every request and be able to call queries and mutations.
For example:
On expired token, I must refetch token and repeat request.
On token damaged, I must logout user and refetch all queries.
And type of error witch I need to handle can be many.
In(react-apollo docs)
networkInterface.useAfter([{
applyAfterware({ response }, next) {
if (response.status === 401) {
logout();
}
next();
}
}]);
I can't access to graphql error, and call queries or mutations.
You can check to see if you have a token before every request is sent. If you do not have a token, you should handle that somewhere else in your application or potentially fetch another straight from the middleware function. You could make higher order component that wraps all of your components that must have a token. If for some reason there is no token, you can fetch another one and store it to localStorage if you are using the browser or asyncstorage if you are using React Native. Once you've assigned it to localStorage or asyncStorage, this middleware code snippet below will check for the token before every request you send, this includes all queries and mutations. If you find that your user doesn't have a token, you could also redirect them in your component them to a page where they must login again and then from there set the token to localstorage or asynstorage. Once again the apollo client's middleware will have access to it that way.
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { checkForSessionToken } from '../../utils/authentication';
const networkInterface = createNetworkInterface({
uri: 'https://localhost:4000'
});
networkInterface.use([{
applyMiddleware(req, next) {
// Create the header object if needed.
if (!req.options.headers) {
req.options.headers = {};
}
// get the authentication token from Async storage
// and assign it to the request object
checkForSessionToken()
.then(SESSION_TOKEN => {
if (SESSION_TOKEN === null || SESSION_TOKEN === undefined {
fetchNewToken()
.then(SESSION_TOKEN => {
localStorage.setItem('token', SESSION_TOKEN);
req.options.headers.Authorization = `Bearer
${SESSION_TOKEN}`;
}
} else {
req.options.headers.Authorization = `Bearer ${SESSION_TOKEN}`;
}
next();
})
.catch(error => {
fetchNewToken()
.then(SESSION_TOKEN => {
localStorage.setItem('token', token);
req.options.headers.Authorization = `Bearer
${SESSION_TOKEN}`;
}
next();
})
}
}]);
const client = new ApolloClient({
networkInterface,
dataIdFromObject: o => o.id
});
export default client;