NextJs Authentication with Next-Auth against DRF - javascript

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.

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.

Auth0 authentication with Cypress

I am trying to create a login command for Cypress and noticed their blog on how to do this does not match the expected values for the Auth0 React SDK. It appears they have used a custom express app to handle the login vs using the SDK to handle this (as per the offical Auth0 documentation).
The Cypress official documentation produces a local storage key value pair that looks like the below.
const item = {
body: {
decodedToken: {
claims,
user: { ... },
audience,
client_id,
},
},
expiresAt: exp,
}
window.localStorage.setItem('auth0Cypress', JSON.stringify(item))
However the one created by the Auth0 React SDK produces something similar to:
const item = {
body: {
access_token,
audience,
client_id,
decodedToken: {
claims,
user: { ... },
encoded,
header
},
expires_in,
id_token,
scope,
token_type
},
expiresAt: exp
}
window.localStorage.setItem(`##auth0spajs##::${client_id}::${audience}::${scope}`, JSON.stringify(item))
I am able to get the https://${auth)_domain}/oauth/token request working, however am not able to work out how to get the data from the response in a way for it to fit the data structure the Auth0 react SDK wants it in.
Has anyone had any success with this?
After doing some exploring, it appears the response I get back from the /oauth/token does not contain all of the fields that the value the Auth0 React SDK outputs when it signs in.
I have also noticed that Auth0 has a guide on how to integrate with Cypress however it does not use this SDK, instead it uses the SPA SDK. That guide also uses a custom login form, where I am using the LockUI.
One thing to note is that I am not using an backend to authenticate (like in most of the examples). I using the loginWithRedirect to login as per the offical recommendation.
After a bit of investigating and help from the Auth0 team, I was successful in making this work.
Here is the code I used:
Cypress.Commands.add("login", () => {
cy.clearLocalStorage();
const email = "";
const password = "";
const client_id = "";
const client_secret = "";
const audience = "";
const scope = "";
cy.request({
method: "POST",
url: "",
body: {
grant_type: "password",
username: email,
password,
audience,
scope,
client_id,
client_secret,
},
}).then(({ body: { access_token, expires_in, id_token, token_type } }) => {
cy.window().then((win) => {
win.localStorage.setItem(
`##auth0spajs##::${client_id}::${audience}::${scope}`,
JSON.stringify({
body: {
client_id,
access_token,
id_token,
scope,
expires_in,
token_type,
decodedToken: {
user: JSON.parse(
Buffer.from(id_token.split(".")[1], "base64").toString("ascii")
),
},
audience,
},
expiresAt: Math.floor(Date.now() / 1000) + expires_in,
})
);
cy.reload();
});
});
});
You have to make sure that the config you pass in is exactly the same as the config you use in the Auth0 Provider.
Once thing to note that tripped me up, was that I was also using refresh tokens. If this is the case make sure to add offline_access to your scope.
I have a public repo to download a working solution - https://github.com/charklewis/auth0-cypress.
There is an example in the Cypress Real World App, a payment application to demonstrate real-world usage of Cypress testing methods, patterns, and workflows in addition to a Auth0 Authentication Testing Strategies Guide which details the changes in to an Auth0 application and the Cypress Real World App.
For those who come across this in the future. We created an alternative approach of testing Auth0 with Cypress which doesn't require changing code in the actual application.
The approach that we use is to run a local service that exposes the same API as Auth0. We packages this service as an NPM package. You can read about it in our blog post https://frontside.com/blog/2022-01-13-auth0-simulator/
Here is what your test end up looking like,
import auth0Config from '../../cypress.env.json';
describe('log in', () => {
it('should get token without signing in', () => {
cy.createSimulation(auth0Config)
.visit('/')
.contains('Log out')
.should('not.exist')
.given({
email: 'bob#gmail.com'
})
.login()
.visit('/')
.contains('Log out')
.logout();
});
});

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.

nestjs firebase authentication

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

React Native + Redux basic authentication

I'm looking for a way to create a basic authentication for my react-native app.
I couldn't find any good example for react-native app.
To login, the app sends the email/password + clientSecret to my server
If OK, the server returns accessToken + refreshToken
The user is logged in, all other requests include the bearer with the accessToken.
If the accessToken expires, the app requests a new one with the refreshToken automatically.
The user stays logged in all the time, the states should be saved in the phone.
What would be the best approach for this?
Thanks.
When an app communicates with a HTTP API which enforces some form of authentication, the app typically follows these steps:
The app is not authenticated, so we prompt the user to log in.
The user enters their credentials (username and password), and taps submit.
We send these credentials to the API, and inspect the response:
On success (200 - OK): We cache the authentication token/ hash, because we're going to use this token/ hash in every subsequent request.
If the token/ hash does not work during any of the subsequent API requests (401 - Unauthorized), we'll need to invalidate the hash/ token and prompt the user to log in again.
Or, on failure (401 - Unauthorized): We display an error message to the user, prompting them re-enter their credentials.
Logging In
Based on the work flow defined above our app starts by displaying a login form, step 2 kicks in when the user taps the login button which dispatches the login action creator below:
/// actions/user.js
export function login(username, password) {
return (dispatch) => {
// We use this to update the store state of `isLoggingIn`
// which can be used to display an activity indicator on the login
// view.
dispatch(loginRequest())
// Note: This base64 encode method only works in NodeJS, so use an
// implementation that works for your platform:
// `base64-js` for React Native,
// `btoa()` for browsers, etc...
const hash = new Buffer(`${username}:${password}`).toString('base64')
return fetch('https://httpbin.org/basic-auth/admin/secret', {
headers: {
'Authorization': `Basic ${hash}`
}
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
// data = { authenticated: true, user: 'admin' }
// We pass the `authentication hash` down to the reducer so that it
// can be used in subsequent API requests.
dispatch(loginSuccess(hash, data.user))
},
(data) => dispatch(loginFailure(data.error || 'Log in failed'))
)
}
}
There's a lot of code in the function above, but take comfort in the fact that
the majority of the code is sanitising the response and can be abstracted away.
The first thing we do is dispatch an action LOGIN_REQUEST which updates our store and lets us know that the user isLoggingIn.
dispatch(loginRequest())
We use this to display an activity indicator (spinning wheel, "Loading...", etc.), and to disable the log in button in our log in view.
Next we base64 encode the user's username and password for http basic auth, and pass it to the request's headers.
const hash = new Buffer(`${username}:${password}`).toString('base64')
return fetch('https://httpbin.org/basic-auth/admin/secret', {
headers: {
'Authorization': `Basic ${hash}`
}
/* ... */
If everything went well, we'll dispatch a LOGIN_SUCCESS action, which results in us having an authentication hash in our store, which we'll use in subsequent requests.
dispatch(loginSuccess(hash, data.user))
On the flip side, if something went wrong then we also want to let the user know:
dispatch(loginFailure(data.error || 'Log in failed')
The loginSuccess, loginFailure, and loginRequest action creators are fairly generic and don't really warrant code samples. See: https://github.com/peterp/redux-http-basic-auth-example/blob/master/actions/user.js)
Reducer
Our reducer is also typical:
/// reducers/user.js
function user(state = {
isLoggingIn: false,
isAuthenticated: false
}, action) {
switch(action.type) {
case LOGIN_REQUEST:
return {
isLoggingIn: true, // Show a loading indicator.
isAuthenticated: false
}
case LOGIN_FAILURE:
return {
isLoggingIn: false,
isAuthenticated: false,
error: action.error
}
case LOGIN_SUCCESS:
return {
isLoggingIn: false,
isAuthenticated: true, // Dismiss the login view.
hash: action.hash, // Used in subsequent API requests.
user: action.user
}
default:
return state
}
}
Subsequent API requests
Now that we have an authentication hash in our store we can pass it into subsequent request's headers.
In our example below we're fetching a list of friends for our authenticated user:
/// actions/friends.js
export function fetchFriends() {
return (dispatch, getState) => {
dispatch(friendsRequest())
// Notice how we grab the hash from the store:
const hash = getState().user.hash
return fetch(`https://httpbin.org/get/friends/`, {
headers: {
'Authorization': `Basic ${hash}`
}
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject({response, json})
}
return json
})
.then(
data => {
// data = { friends: [ {}, {}, ... ] }
dispatch(friendsSuccess(data.friends))
},
({response, data}) => {
dispatch(friendsFailure(data.error))
// did our request fail because our auth credentials aren't working?
if (response.status == 401) {
dispatch(loginFailure(data.error))
}
}
)
}
}
You may find that most API requests typically dispatch the same 3 actions as above: API_REQUEST, API_SUCCESS, and API_FAILURE, and as such the majority of the request/ response code can be pushed into Redux middleware.
We fetch the hash authentication token from the store and setup the request.
const hash = getState().user.hash
return fetch(`https://httpbin.org/get/friends/`, {
headers: {
'Authorization': `Basic ${hash}`
}
})
/* ... */
If the API response with a 401 status code then we've got to remove our hash from the store, and present the user with a log in view again.
if (response.status == 401) {
dispatch(loginFailure(data.error))
}
I've answered the question generically and only dealing with http-basic-auth.
I think that the concept may remain the same, you'll push the accessToken and refreshToken in the store, and extract it in subsequent requests.
If the request fails then you'll have to dispatch another action which updates the accessToken, and then recalls the original request.
I haven't seen too much by way of examples in this area, and think it's definitely something that needs more coverage. I've not yet implemented auth myself, else I'd point you to some code examples. But I can point you to a couple links I've collected that may help you in the right direction...
Oauth 2 with React Native
React Native: Auth0 Login + Firebase
Regardless how you perform your auth, you'll need to securely store your access, refresh, and secret tokens. On iOS I believe you'd do that using keychain and for Android it looks like KeyStore is the way. You may find oblador/react-native-keychain helpful, though it doesn't yet support android it looks like it may support android soon.
I'm actually working on a video tutorial series that answers at least some of the questions your asking. The video along with a transcript and sample code can be found here: http://codecookbook.co/post/how-to-build-a-react-native-login-form-with-redux-pt1/

Categories

Resources