I'm using tokens for auth and want to save them as cookies so it can be used between multiple subdomains.
I use ngx-cookie-service for managing cookies and cookies are only accessible on the subdomain they were saved on.
login(username: string, password: string) {
const token = await this.http.post(loginUrl, { username, password });
this.cookieService.set("token", token, 1, "/", "*.localhost"); // doesn't work
this.cookieService.set("token", token, 1, "/", "localhost"); // doesn't work
this.cookieService.set("token", token, 1, "/", null); // doesn't work
this.cookieService.set("token", token, 1, "/", "account.localhost"); // even this doesn't work
}
How do I save cookies and use them on multiple subdomains?
Related
I have a NestJS backend that exposes the following API:
#Post('sign-in-with-google-account')
async signInWithGoogleAccount(
#Body body: { idToken: string },
#Res({ passthrough: true }) response: Response
) {
const user = await getUserFromGoogleIdToken(body.idToken)
const tokens = await generateAccessAndRefreshTokensForUser(user)
response.cookie('refreshToken', tokens.refreshToken, {
httpOnly: true,
expires: new Date(tokenExpirationDate),
secure: true,
sameSite: 'none'
})
return { accessToken: tokens.accessToken }
}
It receives id token from google oauth, finds the user in the DB and signs a JWT access token and refresh token. The refresh token is stored as httpOnly cookie and the access token is returned.
Now in my next.js app configured with next-auth I have the following:
import GoogleProvider from "next-auth/providers/google";
...
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET
})
]
...
The problem is that next-auth generates its own tokens. But I want next-auth to use my own access and refresh tokens from the NestJS backend, how can I do that?
Also, In NestJS I have a API to refresh the access token like so:
#Get('refresh-access-token')
async refreshAccessToken(#Req() request: Request) {
const accessToken = await getNewAccessTokenFromRefreshToken(request.cookies.refreshToken)
return { accessToken }
}
How can I tell next-auth to refresh the access token using refresh-access-token API every 10 minutes (the access token expiration date)?
I think you need to save the previous time to local storage and then compare it with current time to call the api. You can use moment.unix() or moment.diff() to do this.
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.
I use jwt tokens to authorize actions and to request protected ressources from my backend.
Frontend: Vue, Vue Router, VueX, Vuetify
Backend: Express, jwt tokens, cookieParser
If I successfully call the login route an accessToken and user data (username, email, userId) will be delivered back to the frontend. Additionally to that I send back an httpOnly cookie (containing the refreshToken) and insert the refreshToken on the other side in my database. Via the vuex store I trigger the state.loggedIn to true and assign the user object to state.user. In the same function I save the user object to my localStorage.
To know If a user should be automatically logged in after reopening a new tab, closing window, etc. I read out the data of the localStorage and initiate the vueX store at the beginning with loggedIn and user.
// read out localStorage for the user
const user = JSON.parse(localStorage.getItem('user'));
const initialState = user
? { user, loggedIn: true, accessToken: null }
: { user: null, loggedIn: false, accessToken: null };
Before a protected route will be called I use the beforeEach method on the vue router object to check if my vueX getter auth/getUser returns a user object. If not, the vue router redirects to the login page, so the user can authorize himself again.
const routes = [
{ path: '/login', name: 'Login', component: Login, meta: { authRequired: false }},
{ path: '/home', name: 'Home', component: Home, meta: { authRequired: true }}
]
const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes });
router.beforeEach(async (to, from, next) => {
if (to.meta.authRequired) {
let user = VuexStore.getters['auth/getUser'];
if (!user) {
next({ path: '/login'});
}
}
next();
});
But, who guarantees that the user object in the localStorage is valid and not just set to true. Is this the way to go, or should I use the received accessToken or the cookie with the refreshToken in any way for the auto login process? Thanks.
Yeah you're almost there. What you're looking for is a refresh token:
https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/#silent_refresh
https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
Essentially you want to keep track in your localStorage when the current token expires. And then auto-refresh the token in the background when it's about to. So the user isn't auto-logged out.
Edited to add:
But, who guarantees that the user object in the localStorage is valid and not just set to true. Is this the way to go, or should I use the received accessToken or the cookie with the refreshToken in any way for the auto login process? Thanks.
Your API guarantees that. The second that user will try to interact with your API using an invalid access token, your API throws an error. It won't work. So if a user starts messing with client-side data, that's fine. Just never trust it server-side ;-)
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.
I want to get a token for Graph API using MSAL, but why my token Audience is always pointing out to my Client Id do I need to change the flow to get the token for MSAL? When I tried to get token from postman using password grant_type the audience is Microsoft graph.
here is my configuration
export const authProvider = new MsalAuthProvider({
auth: {
authority: "https://login.microsoftonline.com/tenantId",
clientId: "ClientId",
postLogoutRedirectUri: window.location.origin,
redirectUri: window.location.origin,
validateAuthority: true,
// After being redirected to the "redirectUri" page, should user
// be redirected back to the Url where their login originated from?
navigateToLoginRequestUrl: true
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false
}
},
{
scope: ["https://graph.microsoft.com/.default", "user.read"],
extraQueryParameters: { domain_hint: 'organizations' }
},
{
loginType: LoginType.Redirect,
tokenRefreshUri: window.location.origin + "/auth.html"
},
)
and this is how I get the token
const token = await authProvider.getIdToken();
const idToken = token.idToken.rawIdToken;
and here is the request that got Microsoft Graph
where is the part that I'm wrong? Is it in my configuration or the way I obtain the token?
Simple: it is because you are getting an ID Token, which cannot be used to access a protected resource (such as MS Graph) because:
An ID Token's "audience" (aud) is the client application that requests it.
An ID Token does not have a scope (scp) claim, therefore cannot be exchanged for a resource in v2 endpoint.
There is at least 1 more issue with your configuration:
{
scope: ["https://graph.microsoft.com/.default", "user.read"],
extraQueryParameters: { domain_hint: 'organizations' }
},
The /.default scope allows you to request all the static permissions you have added on Portal in one go. The /.default scope can not/should not be combined with other scopes, especially with v2 scopes like user.read.
Read more about how to work with resources and scopes here (it's meant for MSAL.js, but the same principles apply).