nestjs firebase authentication - javascript

I have nestjs application which uses typeorm and mysql. Now I would like to add firebase for authentication handling, i.e for signup, signin, email verification, forgot password etc.
Plans is create user first in firebase, then same user details will be added into mysql user table for further operaiton. So for this I am using customized middleware
#Injectable()
export class FirebaseAuthMiddleware implements NestMiddleware {
async use(req: Request, _: Response, next: Function) {
const { authorization } = req.headers
// Bearer ezawagawg.....
if(authorization){
const token = authorization.slice(7)
const user = await firebase
.auth()
.verifyIdToken(token)
.catch(err => {
throw new HttpException({ message: 'Input data validation failed', err }, HttpStatus.UNAUTHORIZED)
})
req.firebaseUser = user
next()
}
}
}
Full code is available in Github
Problem with above code is that, it always looks for auth token, const { authorization } = req.headers const token = authorization.slice(7)
However, when user first time access application, authorization header always be null.
example if user access signup page we cannot pass auth header.
please let me know how can I modify above code when user access signup page, it allows user create user firebase, then same details can be stored in database.

We can exclude the routes for which you don't want this middleware.
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes(CatsController);

You can just next() to skip this middleware if there is no authorization. so it s okay to access sign up api when no authorization

Related

In JavaScript, how can we use the Microsoft Authentication Library to request a JWT token for a client connection with an an integration user?

We have a microservice, composed in JavaScript, which needs to consume a second microservice. The second microservice requires the consuming application to provide a JWT token which claims
"roles": [
"FooBar.Read"
],
for permission to use the service.
Rather than reinvent the wheel when calling Azure Active Directory to obtain and cache the token, we'd like to make use of the Microsoft Authentication Library node package.
I think we probably want to use the acquireTokenSilent() method of the ConfidentialClientApplication, but I'm not entirely clear how to create the request.
I've created this module:
import msal from '#azure/msal-node';
import {cachePlugin} from 'token-cache';
const confidentialClient = new msal.ConfidentialClientApplication({
auth: {
authority: `${process.env.AZURE_ACTIVE_DIRECTORY_AUTHORITY_URI}/${process.env.AZURE_ACTIVE_DIRECTORY_TENANT_ID}`,
clientId: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_ID,
clientSecret: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_SECRET,
knownAuthorities: [],
},
cache: {
cachePlugin,
},
system: {
loggerOptions: {
loggerCallback(loglevel, message) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
},
},
});
const silentFlowRequest = {
account: {
tenantId: process.env.AZURE_ACTIVE_DIRECTORY_TENANT_ID,
username: process.env.AZURE_ACTIVE_DIRECTORY_USERNAME,
password: process.env.AZURE_ACTIVE_DIRECTORY_PASSWORD,
},
scopes: [process.env.AZURE_ACTIVE_DIRECTORY_EMPLOYEE_MANAGEMENT_SCOPE]
};
async function acquireToken() {
try {
return await confidentialClient.acquireTokenSilent(silentFlowRequest)
}
catch (error) {
console.error(error);
}
}
module.exports = {
acquireToken
};
However, I expect it to fail because Intell-J tells me:
Argument type {scopes: string[], account: {password: string, tenantId: string, username: string}} is not assignable to parameter type SilentFlowRequest
What is the correct way to do this?
Thanks for reaching out to us, please follow the doc - https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/AcquireTokenSilentAsync-using-a-cached-token#recommended-pattern-to-acquire-a-token .
hope this will help you.
You can use the MSAL's client credentials grant using a client secret in order to acquire tokens for your web API. We have a code sample with a fairly explanatory README here.
The client credentials grant first acquires a token (through ConfidentialClientApplicaiton.acquireTokenByClientCredentials) making a network request to AzureAD. Once the token is acquired, it is cached automatically by MSAL and subsequent calls will return the same token from the cache until it expires, at which point MSAL will refresh the token for you.
// Create msal application object
const confidentialClientApplication = new msal.ConfidentialClientApplication(
{
authOptions: {
clientId: "<ENTER_CLIENT_ID>",
authority: "https://login.microsoftonline.com/<ENTER_TENANT_ID>",
clientSecret: "<ENTER_CLIENT_SECRET>"
}
});
// Acquire tokens
function getClientCredentialsToken(confidentialClientApplication, scopes) {
// With client credentials flows permissions need to be granted in the portal by a tenant administrator.
// The scope is always in the format "<resource>/.default"
const clientCredentialRequest = {
scopes: scopes
};
return confidentialClientApplication
.acquireTokenByClientCredential(clientCredentialRequest)
.then((response) => {
// Handle response
}).catch((error) => {
// Handle error
});
}
Essentially, you create a client secret on the Azure Portal and then place it in your MSAL configuration. This secret is used in place of user credentials, allowing your application to authenticate with AzureAD and acquire tokens without any user interaction.

Most ideal way to call firebase getIdToken

i am implementing user authentication with the help of firebase in my React project. So, I am confused over something.
I am verifying the user from firebase and then getting a token on frontend which is sent to backend via headers and verfied there once.
I read the docs and came to know that firebase token gets expired after 1 hr by default so we have to use "getIdToken" like
firebase.auth().onAuthStateChanged(async user => {
if (user) {
console.log(user, 'user123 inside firebaseAuth')
const token = await user.getIdToken()
Cookies.set('my_token', token, { domain: domain })
}
})
but how do i manage this function , do i have to call it everytime the component updates or everytime before hitting api or first time the component renders ?
The thing is i do not want this token to get expire until the user logs out himself / herself even if he is in a different component and sitting ideal for too long.
You can get the Firebase ID Token every time you are making an API call to your server:
async function callAPI() {
const user = firebase.auth().currentUser
if (user) {
const token = await user.getIdToken()
const res = await fetch("url", {
headers: {authorization: `Bearer ${token}`}
})
} else {
console.log("No user is logged in")
}
}
You could get the ID token once when the component mounts but then you'll have to deal with onIdTokenChanged to keep it updated in your state. Using the method above you'll get a valid token always.

Error using AWS Cognito for authentication with Hasura

i'm having some problems using lambda enviroment.
Looking to set a function that make a mutation to Hasura so I can relate Auth users of Cognito with my app information.
I set the following function Post Authentication in Lamba but it does not work.
function Add(event, context, callback) {
const userId = event.user_id;
const hasuraAdminSecret = "xxx";
const url = "xxx";
const upsertUserQuery = `
mutation($userId: String!){
insert_RegistroAnimal_users(objects: [{ id: $userId }], on_conflict: { constraint: users_pkey, update_columns: [] }) {
affected_rows
}
}`
const graphqlReq = { "query": upsertUserQuery, "variables": { "userId": userId } }
request.post({
headers: {'content-type' : 'application/json', 'x-hasura-admin-secret': hasuraAdminSecret},
url: url,
body: JSON.stringify(graphqlReq)
}, function(error, response, body){
console.log(body);
callback(null, user, context);
});
}
Followed this tutorial : https://hasura.io/docs/latest/graphql/core/guides/integrations/aws-cognito.html#introduction
What do you think is wrong with the code?
I don't think anything is wrong with the code, but to make it work with Cognito you'd need to provide your Hasura setup with a JWT claims function as shown in that same guide, https://hasura.io/docs/latest/graphql/core/guides/integrations/aws-cognito.html#create-a-lambda-function-to-add-claims-to-the-jwt. If you'd like to do it as the guide suggests, you need to create a lambda function like so;
exports.handler = (event, context, callback) => {
event.response = {
"claimsOverrideDetails": {
"claimsToAddOrOverride": {
"https://hasura.io/jwt/claims": JSON.stringify({
"x-hasura-user-id": event.request.userAttributes.sub,
"x-hasura-default-role": "user",
// do some custom logic to decide allowed roles
"x-hasura-allowed-roles": ["user"],
})
}
}
}
callback(null, event)
}
You then need to pick this function as the PreTokenGeneration trigger from your user pool settings. Then AWS Cognito will trigger this function before token generation, allowing you to add Hasura required claims to your token.
The next step is to tell Hasura where to lookup for the JWT claims by providing HASURA_GRAPHQL_JWT_SECRET during the setup, which is essentially an URL pointing to your Cognito setup, generated using the pool id.
Finally, you can obtain the idToken from your user session after a successful login, and pass that token as an Authentication header for your Hasura requests. Described here.
All of these steps were actually described in the guide you linked, but may not be as clear. I believe the reason your current setup does not work is that your Hasura setup is missing the HASURA_GRAPHQL_ADMIN_SECRET, which needs to be the same as the x-hasura-admin-secret you're using in your requests.
Mind you, if you use x-hasura-admin-secret in your app and expose it to your users which gives them admin access, that creates a potential security issue and anyone with that secret can wipe up your data. x-hasura-admin-secret should be reserved for your admin tasks and not used in an app where AWS Cognito authentication is planned to be used.

NextJs Authentication with Next-Auth against DRF

I have an exiting Django project that I am trying to move from templates to NextJs frontend. I came across Next-Auth-js which seems to be nice in Next Auth.
However, the doc seems to focus more with JS related Backend Auth. Following this example I have sent the NEXTAUTH_URL environment variable to my DRF Endpoint localhost:8002. While the frontend runs on localhost:3000. While my _app.js looks like this:
<Provider options={{site: process.env.NEXTAUTH_URL,}} session={pageProps.session} >
<Component {...pageProps} />
</Provider>
Using the Nav.js for a test, I changed the signin/out href to point to my Django endpoints but it seems next-auth-js ignores this and places a session fetch to my frontend http://localhost:3000/api/auth/session instead of the the http://localhost:8002/api/auth/session.
I will appreciate any assistance on how I can correctly/securely implement this authentication using Django Rest Framework (DRF)
I think that is the way it should work, your nextjs site would be a kind of proxy/middleware to your django API client -> nextjs -> DRF, you should let it handle the sessions and for any action you need to do in your API for any authentication step, put code to hit those endpoints in the callbacks or events configuration, I think this tutorial is more accurate for your use case
from the docs
pages/api/auth/[...nextauth].js
import Providers from `next-auth/providers`
...
providers: [
Providers.Credentials({
// The name to display on the sign in form (e.g. 'Sign in with...')
name: 'Credentials',
// The credentials is used to generate a suitable form on the sign in page.
// You can specify whatever fields you are expecting to be submitted.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
username: { label: "Username", type: "text", placeholder: "jsmith" },
password: { label: "Password", type: "password" }
},
authorize: async (credentials) => {
// Add logic here to look up the user from the credentials supplied
const user = { id: 1, name: 'J Smith', email: 'jsmith#example.com' }
if (user) {
// call your DRF sign in endpoint here
// Any object returned will be saved in `user` property of the JWT
return Promise.resolve(user)
} else {
// If you return null or false then the credentials will be rejected
return Promise.resolve(null)
// You can also Reject this callback with an Error or with a URL:
// return Promise.reject(new Error('error message')) // Redirect to error page
// return Promise.reject('/path/to/redirect') // Redirect to a URL
}
}
})
]
...
events: {
signOut: async (message) => { /* call your DRF sign out endpoint here */ },
}
You can use callbacks here. https://next-auth.js.org/configuration/callbacks
callbacks: {
async signIn(user, account, profile) {
return true
},
async redirect(url, baseUrl) {
return baseUrl
},
async session(session, user) {
return session
},
async jwt(token, user, account, profile, isNewUser) {
return token
}
}
in signIn callback, you can get accessToken and tokenId from provider login. Here, call your DRF API and pass those tokens to your DRF and when you get back the access_token and refresh_token from DRF. Add them to your user instance. And then in JWT callback, get the access and refresh from user and add them into token
Got this from some blog
Though, you also need to handle the refresh token.

How do I get the current user's access token in AngularFire2?

In AngularFire you were able to access the providers (e.g Google) accessToken for the authenticated user.
There does not seem to be a way to access this with AngularFire2?
On initial login say like this:
this.af.auth.subscribe(user=> {
if (user) {
console.log(user.google);
}
});
It will log out the idToken, accessToken, provider,
But (on a page refresh) subsequently will log out the standard details (uid, displayName etc....)
And the accessToken is not an accessible property?
Is there a way to access the current users accessToken?
getToken is deprecated now. You should use getIdToken instead:
this.af.auth.currentUser.getIdToken(true)
.then((token) => localStorage.setItem('tokenId', token));
The access token is only accessible when the user first signs in. From the Firebase migration guide for web developers:
With the Firebase.com Authentication API, you can easily use the provider's access token to call out to the provider's API and get additional information. This access token is still available, but only immediately after the sign-in action has completed.
var auth = firebase.auth();
var provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider).then(function(result) {
var accessToken = result.credential.accessToken;
});
So it is indeed not available on a page refresh. If you want it to remain available, you will need to persist it yourself.
With AngularFire2, you can get the token like this :
this.af.auth.getToken() // returns a firebase.Promise<any>
If you want to get an ES6 Promise instead, just use
Promise.resolve()
.then(() => this.af.auth.getToken() as Promise<string>)
This works for me:
this.af.auth.getAuth().auth.getToken(false);//true if you want to force token refresh
You can put it into a Service and then you can get the token like this:
this.authService.getToken().then(
(token) => console.debug(`******** Token: ${token}`));
Getting the auth token from storage in angularfire2
JSON.parse(JSON.stringify(this.afAuth.auth.currentUser)).stsTokenManager.accessToken
As seen in this discussion:
https://github.com/angular/angularfire2/issues/725
With AngularFire2 : ( eg : registering user with email and password combo. )
import { AngularFireAuth } from 'angularfire2/auth';
model : any = {} ;
private afAuth : AngularFireAuth,
regWithEP () {
this.afAuth.auth.createUserWithEmailAndPassword(this.model.email, this.model.password).then((user) => {
/* IMPORTANT !! */
/* EXPLICIT CHECK IF USER IS RETURNED FROM FIREBASE SERVICE !! */
if (user) {
console.log(user);
/* Here user is available and is the same as auth.currentUser */
this.afAuth.auth.currentUser.getToken().then((token) => {
//token is available here
//set token to currentUser, signIn , re-direct etc.
});
}
});
}

Categories

Resources