Passport and Auth::logout() produces an unexpected error - laravel-passport

I am using Laravel 6.7 with Passport to consume my own API. When I try to logout a user using Auth::logout(), I get the following error:
Illuminate\Auth\RequestGuard::logout does not exist.
I don't understand why I get such behavior. I haven't used any custom guards. My Auth.php as per the Passport Setup is as follows:
<?php
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
This is my AuthenticationController.php where the logout code resides:
/**
* --------------------------------------------------
* Removes the identity of a users login session.
* --------------------------------------------------
* #param Request $request
* #return MessageResource
* --------------------------------------------------
*/
public function logout(Request $request): MessageResource
{
if (Auth::check()) {
Auth::user()->token()->revoke();
}
return new MessageResource(['message' => 'Logout request is successful.']);
}
Is there any way to solve this issue? Any insights would be greatly appreciated. Thanks in advance.

Auth::logout() is for web guard.Here, you are using API guard so delete token of authorization it'll automatically logout from you application.
public function logout(Request $request)
{
$request->user()->token()->revoke();
//$request->user()->token()->delete(); for delete.
//Auth::user()->token()->revoke(); same way as revoke user token
return response()->json([
'message' => 'Successfully logged out'
]);
}

Try this
public function logout(Request $request)
{
$request->user()->token()->revoke();
return response()->json([
'message' => 'Successfully logged out'
]);
}

Related

Fastify pass custom parameter to preHandler

I'm just learning fastify and I'm not sure how to achieve what I want:
I have this route:
this.fastify.get('/ping', {
preHandler: [
this.fastify.jwtVerify,
],
}, this.configHandler.getConfiguration.bind(this.configHandler));
The pre handler does get executed and contains the known parameters like the request and the reply.
I want to pass a custom parameter to my preHandler function. Currently the preHandler is verifying the jwt token passed in the auth header. What I want to achieve is to pass scopes to the handler which may also be checked.
My preHandler currently is a plugin registered like this:
const jwtVerifyPlugin: FastifyPluginAsync = async (fastify: FastifyInstance, options: FastifyPluginOptions) => {
fastify.decorate('jwtVerify', async function (request: FastifyRequest, reply: FastifyReply) {
//jwtVerficiation happens here
//scope verification should follow
})
}
So overall: I have to add scopes somewhere at the route and I have to get those scopes somwhere inside my preHandler.
Any idea how I can do that?
Thanks!
You can define your decorate function like this:
const jwtVerifyPlugin: FastifyPluginAsync = async (fastify: FastifyInstance, options: FastifyPluginOptions) => {
fastify.decorate('jwtVerify', function (options?: { scopes?: string[] }) {
return async function (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) {
if (options?.scopes) {
// access scopes here
}
done();
};
})
}
and then use it like this:
this.fastify.get('/ping', {
preHandler: [
this.fastify.jwtVerify({ scopes: ['admin'] }),
],
}, this.configHandler.getConfiguration.bind(this.configHandler));

Angular/NodeJS share frontend access token to fetch data from MS graph by backend

I have a small MEAN stack running (Angular in Frontend and NodeJS in Backend). The Frontend is protected by MSAL (#azure/msal-angular).
This part is working fine. The user gets authorized for the frontend and Angular is able to request data from MS Graph (the msal interceptor adds the token to all requests to the MS Graph and the backend):
app.module.ts
MSalModule.forRoot( new PublicClientApplication({ // MSAL Configuration
auth: {
clientId: environment.aad_client_id,
authority: 'https://login.microsoftonline.com/' + environment.aad_tenant_id + '/',
redirectUri: window.location.origin,
},
cache: {
cacheLocation : BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE,
}
}), {
// MSAL Guard Configuration
interactionType: InteractionType.Redirect,
authRequest: {
scopes: ['user.read', environment.aad_scope_api]
}
}, {
// MSAL Interceptor Configuration
interactionType: InteractionType.Redirect,
protectedResourceMap: new Map([
['https://graph.microsoft.com/v1.0', ['user.read']],
[environment.apiUrl, [environment.aad_scope_api]],
])
})
After redirect from MS login I send a post request to my NodeJS Backend to establish a session.
The login route of the Backend should extract the token from the header, and send some request to the graph, to store the user details from there in the user session.
login.js
router.post('/login', (req, res) => {
if (req.session.user) {
res.json(req.session.user);
} else {
fetchUser(req, mongodb).then(result => {
req.session.user = result;
res.json(result);
}).catch(err => {
res.status(401).json(err);
})
}
});
...
async function fetchUser(token) {
try {
const token = req.headers.authorization;
request({
headers: { 'Authorization': token },
uri: 'https://graph.microsoft.com/v1.0/me',
method: 'GET'
}, { json: true }, (err, res, body) => {
if (err) { throw err; }
const obj = ...do some things
return obj;
});
} catch(err) {
throw err;
}
}
The issue is, that the token is only valid from Frontend. MS recommend the on-behalf-of-flow for that, but I'm not able to find any way to solve this. So how can I request a new token for my backend?
You can request a token for the backend to access Graph using the client credentials authentication, and set the scopes for Graph as Application Permissions on the App Registration, such as User.Read.All.
You would instead read the "oid" from the AAD access token passed from frontend to backend for discovering the user for formatting requests to Graph. Microsoft created a tutorial on implementing which you may find helpful.

Secure a GraphQL API with passport + JWT's or sessions? (with example)

To give a bit of context: I am writing an API to serve a internal CMS in React that requires Google login and a React Native app that should support SMS, email and Apple login, I am stuck on what way of authentication would be the best, I currently have an example auth flow below where a team member signs in using Google, a refresh token gets sent in a httpOnly cookie and is stored in a variable in the client, then the token can be exchanged for an accessToken, the refresh token in the cookie also has a tokenVersion which is checked before sending an accessToken which does add some extra load to the database but can be incremented if somebody got their account stolen, before any GraphQL queries / mutations are allowed, the user's token is decoded and added to the GraphQL context so I can check the roles using graphql-shield and access the user for db operations in my queries / mutations if needed
Because I am still hitting the database even if it's only one once on page / app load I wonder if this is a good approach or if I would be better off using sessions instead
// index.ts
import "./passport"
const main = () => {
const server = fastify({ logger })
const prisma = new PrismaClient()
const apolloServer = new ApolloServer({
schema: applyMiddleware(schema, permissions),
context: (request: Omit<Context, "prisma">) => ({ ...request, prisma }),
tracing: __DEV__,
})
server.register(fastifyCookie)
server.register(apolloServer.createHandler())
server.register(fastifyPassport.initialize())
server.get(
"/auth/google",
{
preValidation: fastifyPassport.authenticate("google", {
scope: ["profile", "email"],
session: false,
}),
},
// eslint-disable-next-line #typescript-eslint/no-empty-function
async () => {}
)
server.get(
"/auth/google/callback",
{
preValidation: fastifyPassport.authorize("google", { session: false }),
},
async (request, reply) => {
// Store user in database
// const user = existingOrCreatedUser
// sendRefreshToken(user, reply) < send httpOnly cookie to client
// const accessToken = createAccessToken(user)
// reply.send({ accessToken, user }) < send accessToken
}
)
server.get("/refresh_token", async (request, reply) => {
const token = request.cookies.fid
if (!token) {
return reply.send({ accessToken: "" })
}
let payload
try {
payload = verify(token, secret)
} catch {
return reply.send({ accessToken: "" })
}
const user = await prisma.user.findUnique({
where: { id: payload.userId },
})
if (!user) {
return reply.send({ accessToken: "" })
}
// Check live tokenVersion against user's one in case it was incremented
if (user.tokenVersion !== payload.tokenVersion) {
return reply.send({ accessToken: "" })
}
sendRefreshToken(user, reply)
return reply.send({ accessToken: createAccessToken(user) })
})
server.listen(port)
}
// passport.ts
import fastifyPassport from "fastify-passport"
import { OAuth2Strategy } from "passport-google-oauth"
fastifyPassport.registerUserSerializer(async (user) => user)
fastifyPassport.registerUserDeserializer(async (user) => user)
fastifyPassport.use(
new OAuth2Strategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:4000/auth/google/callback",
},
(_accessToken, _refreshToken, profile, done) => done(undefined, profile)
)
)
// permissions/index.ts
import { shield } from "graphql-shield"
import { rules } from "./rules"
export const permissions = shield({
Mutation: {
createOneShopLocation: rules.isAuthenticatedUser,
},
})
// permissions/rules.ts
import { rule } from "graphql-shield"
import { Context } from "../context"
export const rules = {
isAuthenticatedUser: rule()(async (_parent, _args, ctx: Context) => {
const authorization = ctx.request.headers.authorization
if (!authorization) {
return false
}
try {
const token = authorization.replace("Bearer", "")
const payload = verify(token, secret)
// mutative
ctx.payload = payload
return true
} catch {
return false
}
}),
}
To answer your question directly, you want to be using jwts for access and that's it. These jwts should be created tied to a user session, but you don't want to have to manage them. You want a user identity aggregator to do it.
You are better off removing most of the code to handle user login/refresh and use a user identity aggregator. You are running into common problems of the complexity when handling the user auth flow which is why these exist.
The most common is Auth0, but the price and complexity may not match your expectations. I would suggest going through the list and picking the one that best supports your use cases:
Auth0
Okta
Firebase
Cognito
Authress
Or you can check out this article which suggests a bunch of different alternatives as well as what they focus on

Setup test environment for Strapi project

I am trying to setup unit test for a Strapi project my code looks like below
test_utils.js
const Strapi = require("strapi");
const http = require('http');
let instance; // singleton
jest.setTimeout(10000)
async function setupStrapi() {
if (!instance) {
instance = Strapi()
await instance.load();
// Run bootstrap function.
await instance.runBootstrapFunctions();
// Freeze object.
await instance.freeze();
instance.app.use(instance.router.routes()).use(instance.router.allowedMethods());
instance.server = http.createServer(instance.app.callback());
}
return instance;
}
module.exports = { setupStrapi }
controllers.test.js
const request = require("supertest")
const {setupStrapi, setupUser} = require("../../test_utils")
describe("chat-group controllers", ()=>{
let strapi
beforeAll(async ()=>{
strapi = await setupStrapi()
})
test("endpoint tasks", async (done)=>{
app = strapi
app.server.listen(app.config.port, app.config.host)
const resp = await request(app.server).get("/testpublics")
.expect(200)
console.log(resp.body)
done()
})
})
when I run the test, I get 403 error on "/testpublics". Note that "/testpublics" is public api and I can access it from browser.
I think the problem is with setupStrapi function, I took the code from node_modules/strapi/lib/strapi.js file.
What is the better way to setup unit test for Strapi project. I want to achieve following
start test with clean database each time
test public and authenticated api endpoints
I encountered the same problem. Go to ./api/name-of-your-api/config/routes.json and remove the config property for each of the endpoints.
It should be this:
{
"routes": [
{
"method": "GET",
"path": "/testpublics",
"handler": "testpublics.index"
},
}
as opposed to this:
{
"routes": [
{
"method": "GET",
"path": "/testpublics",
"handler": "testpublics.index",
"config": {
"policies": []
}
},
}
If you want this route to by public by policy, answer from #sama-bala resolves everything.
For Strapi the custom route and controller that is not public (needs JWT token in Request header) must be assigned to role — otherwise even for a valid token controller will throw Forbidden 403 error. The whole process is described in Authenticated request tutorial on Strapi documentation page. This information is saved in the database only. Usually you do this in the admin panel, not from source code.
Have a look on the following snippet
/**
* Grants database `permissions` table that role can access an endpoint/controllers
*
* #param {int} roleID, 1 Autentihected, 2 Public, etc
* #param {string} value, in form or dot string eg `"permissions.users-permissions.controllers.auth.changepassword"`
* #param {boolean} enabled, default true
* #param {string} policy, default ''
*/
const grantPrivilage = async (
roleID = 1,
value,
enabled = true,
policy = ""
) => {
const updateObj = value
.split(".")
.reduceRight((obj, next) => ({ [next]: obj }), { enabled, policy });
return await strapi.plugins[
"users-permissions"
].services.userspermissions.updateRole(roleID, updateObj);
};
It allows you to assign route to role programatically by updating the database. In case of your code the solution might look like that
await grantPrivilage(2, "permissions.application.controllers.testpublics.index"); // 1 is default role for Autheticated user, 2 is Public role.
You can add this in beforeAll or in bootstrap.js, eg
beforeAll(async (done) => {
user = await userFactory.createUser(strapi);
await grantPrivilage(1, "permissions.application.controllers.hello.hi");
done();
});
I've tried to explore this topic in my blog post

JWT authentication in Node.js + express-graphql + passport

I'm writing a fullstack application using MERN and I need to provide authentication using JWT-tokens. My code looks like:
router.use(GraphQLHTTP(
(req: Request, res: Response): Promise<any> => {
return new Promise((resolve, reject) => {
const next = (user: IUser, info = {}) => {
/**
* GraphQL configuration goes here
*/
resolve({
schema,
graphiql: config.get("isDev"), // <- only enable GraphiQL in production
pretty: config.get("isDev"),
context: {
user: user || null,
},
});
};
/**
* Try to authenticate using passport,
* but never block the call from here.
*/
passport.authenticate(['access'], { session: false }, (err, loginOptions) => {
next(loginOptions);
})(req, res, next);
})
}));
I want to provide a new generation of tokens and through GraphQL. In doing so, I need to check whether the user has used the correct method of authentication. For example, to get a new access token, you need a refresh token, you need to log in using the password and e-mail for the refresh token. But using a passport implies that after authentication I will simply have a user.
How should I proceed?

Resources