Problem with JWT verification in Next.js and Spring Boot - spring-boot

I am trying to make protected routes in Next.js using middleware:
import { type NextRequest, NextResponse } from 'next/server';
import { verifyAuth } from '#helpers/auth/verifyJWT';
export const config = {
matcher: ['/dash', '/auth/:path*'],
};
export async function middleware(req: NextRequest) {
const verifiedToken = await verifyAuth(req).catch((err) => {
console.error(err.message);
});
if (!verifiedToken) {
if (req.nextUrl.pathname.startsWith('/dash')) {
return NextResponse.redirect(new URL('/auth/login', req.url));
} else {
return NextResponse.next();
}
} else {
if (req.nextUrl.pathname.startsWith('/auth')) {
return NextResponse.redirect(new URL('/dash', req.url));
} else {
return NextResponse.next();
}
}
}
Here is verifyAuth method:
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';
import { USER_TOKEN, getJwtSecretKey } from '../constatns';
interface UserJwtPayload {
jti: string;
iat: number;
}
export class AuthError extends Error {}
export async function verifyAuth(req: NextRequest) {
const token = req.cookies.get(USER_TOKEN)?.value;
if (!token) throw new AuthError('Missing user token ');
try {
const verified = await jwtVerify(
token,
new TextEncoder().encode(getJwtSecretKey()),
);
return verified.payload as UserJwtPayload;
} catch (err) {
throw new AuthError('Your token has expired.');
}
}
Problem is that I always get the message: Token is expired.
I am using Spring Boot for backend part, here is my SpringSecurity part for signing token:
public String generateToken(
Map<String, Object> extraClaims,
UserDetails userDetails
) {
return Jwts
.builder()
.setClaims(extraClaims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 24))
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
.compact();
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
return Keys.hmacShaKeyFor(keyBytes);
}
So I tried many libraries to code/encode my secret key in the Nextjs app but same error.
I am not sure how to do it.
Is there any other solution?

Related

Property 'pipe' does not exist on type 'Promise

I`m developing auth guard in nestjs, I got This error.
If I call .pipe()
async validateUser(email: string, password: string): Promise<User> {
const findUser = await this.userRepository.findOneBy({email: email, password: password });
if (!findUser) throw new NotFoundException("user not found");
return findUser;
}
login(user: User): Observable<string> {
const { email, password } = user;
return this.validateUser(email, password).pipe(
switchMap((user: User) => {
if (user) {
// create JWT - credwntials
return from(this.jwtServices.signAsync({ user }));
}
}),
);
}
This my authService.
You need transform promise to observable using from()
import { from } from 'rxjs';
login(user: User): Observable<string> {
const { email, password } = user;
return from(this.validateUser(email, password)).pipe(
switchMap((user: User) => {
if (user) {
// create JWT - credwntials
return from(this.jwtServices.signAsync({ user }));
}
}),
);

"res.setHeader is not a function" error Google Auth Strategy in NestJS GraphQL

I've tried to implement an oauth method using GraphQL with Google auth and for some reason I'm getting the following error
"res.setHeader is not a function" from within the authenticate method in Google Strategy
I've used passport-google-oauth20 strategy
this is my google-auth.guard.ts
import { ExecutionContext, Injectable } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class GoogleAuthGuard extends AuthGuard('google') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const gqlReq = ctx.getContext().req;
if (gqlReq) {
const { token } = ctx.getArgs();
gqlReq.body = { token };
return gqlReq;
}
return context.switchToHttp().getRequest();
}
}
this is my google.strategy.ts
import { PassportStrategy } from '#nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { Profile } from 'passport';
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor() {
super({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_SECRET,
callbackURL: process.env.GOOGLE_REDIRECT_URL,
prompt: 'consent',
scope: ['email', 'profile'],
})
}
async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifyCallback,
): Promise<any> {
if (!profile) {
return done(new UnauthorizedException(), false);
}
return done(null, profile);
}
}
it's important to point out that since my app is a react SPA, the callbackURL value is the main page of the client and not another path in the server.
and the resolver which I intend to use to generate a jwt token and a refresh token, but the code never gets to this part due to the error in the strategy
#UseGuards(GoogleAuthGuard)
#Query(() => LoginResponseApi)
async googleLogin(
#Args({ name: 'token', type: () => String }) token: string,
#Req() req,
#Context() context
): Promise<LoginResponseApi> {
const res: Response = context.req.res;
const loginResponse: any = await this.authService.googleLogin(req)
const jwtToken = this.authService.createRefreshToken(loginResponse.user)
if (loginResponse.accessToken)
this.authService.sendRefreshToken(res, jwtToken)
return loginResponse;
}

NestJS/GraphQL/Passport - getting unauthorised error from guard

I'm trying to follow along with this tutorial and I'm struggling to convert the implementation to GraphQL.
local.strategy.ts
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authenticationService: AuthenticationService) {
super();
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authenticationService.getAuthenticatedUser(
email,
password,
);
if (!user) throw new UnauthorizedException();
return user;
}
}
local.guard.ts
#Injectable()
export class LogInWithCredentialsGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
req.body = ctx.getArgs();
await super.canActivate(new ExecutionContextHost([req]));
await super.logIn(req);
return true;
}
}
authentication.type.ts
#InputType()
export class AuthenticationInput {
#Field()
email: string;
#Field()
password: string;
}
authentication.resolver.ts
#UseGuards(LogInWithCredentialsGuard)
#Mutation(() => User, { nullable: true })
logIn(
#Args('variables')
_authenticationInput: AuthenticationInput,
#Context() req: any,
) {
return req.user;
}
mutation
mutation {
logIn(variables: {
email: "email#email.com",
password: "123123"
} ) {
id
email
}
}
Even the above credentials are correct, I'm receiving an unauthorized error.
The problem is in your LogInWithCredentialsGuard.
You shouldn't override canAcitavte method, all you have to do is update the request with proper GraphQL args because in case of API request, Passport automatically gets your credentials from req.body. With GraphQL, execution context is different, so you have to manually set your args in req.body. For that, getRequest method is used.
As the execution context of GraphQL and REST APIs is not same, you have to make sure your guard works in both cases whether it's controller or mutation.
here is a working code snippet
#Injectable()
export class LogInWithCredentialsGuard extends AuthGuard('local') {
// Override this method so it can be used in graphql
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const gqlReq = ctx.getContext().req;
if (gqlReq) {
const { variables } = ctx.getArgs();
gqlReq.body = variables;
return gqlReq;
}
return context.switchToHttp().getRequest();
}
}
and your mutation will be like
#UseGuards(LogInWithCredentialsGuard)
#Mutation(() => User, { nullable: true })
logIn(
#Args('variables')
_authenticationInput: AuthenticationInput,
#Context() context: any, // <----------- it's not request
) {
return context.req.user;
}
I've been able to get a successful login with a guard like this:
#Injectable()
export class LocalGqlAuthGuard extends AuthGuard('local') {
constructor() {
super();
}
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const req = ctx.getContext().req;
req.body = ctx.getArgs();
return req;
}
async canActivate(context: ExecutionContext) {
await super.canActivate(context);
const ctx = GqlExecutionContext.create(context);
const req = ctx.getContext().req;
await super.logIn(req);
return true;
}
}

Angular, error 500 after sending the request in the header

I have a hard time passing the right angular request to the header. This is my service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpHeaders }
from '#angular/common/http';
import { Utente } from '../model/Utente ';
import { Prodotto } from '../model/Prodotto ';
import { OktaAuthService } from '#okta/okta-angular';
import { Observable, from } from 'rxjs';
import { Carrello } from '../model/Carrello ';
import { userInfo } from 'node:os';
import { getLocaleCurrencyCode } from '#angular/common';
const headers = new HttpHeaders().set('Accept', 'application/json');
#Injectable({
providedIn: 'root'
})
export class HttpClientService {
constructor(
private httpClient:HttpClient, private oktaAuth:OktaAuthService ) {}
getCarr(){
return this.httpClient.get<Carrello[]>('http://localhost:8080/prodotti/utente/vedicarrelloo', {headers} );
}
}
This is my spring method:
#Transactional(readOnly = true)
public List<Carrello> getCarrello(#AuthenticationPrincipal OidcUser utente){
Utente u= utenteRepository.findByEmail(utente.getEmail());
return carrelloRepository.findByUtente(u);
}
In console I get this error (error 500):
https://i.stack.imgur.com/BiONS.png
this error corresponds in my console to "java.lang.NullPointerException: null.
But if I access localhost: 8080, I can see the answer correctly, so I assume there is a problem in passing the request header in angular, can anyone tell me where am I wrong, please? I specify that I get this error only in the methods where the OidcUser is present, the rest works perfectly. Thank you!
You need to send an access token with your request. Like this:
import { Component, OnInit } from '#angular/core';
import { OktaAuthService } from '#okta/okta-angular';
import { HttpClient } from '#angular/common/http';
import sampleConfig from '../app.config';
interface Message {
date: string;
text: string;
}
#Component({
selector: 'app-messages',
templateUrl: './messages.component.html',
styleUrls: ['./messages.component.css']
})
export class MessagesComponent implements OnInit {
failed: Boolean;
messages: Array<Message> [];
constructor(public oktaAuth: OktaAuthService, private http: HttpClient) {
this.messages = [];
}
async ngOnInit() {
const accessToken = await this.oktaAuth.getAccessToken();
this.http.get(sampleConfig.resourceServer.messagesUrl, {
headers: {
Authorization: 'Bearer ' + accessToken,
}
}).subscribe((data: any) => {
let index = 1;
const messages = data.messages.map((message) => {
const date = new Date(message.date);
const day = date.toLocaleDateString();
const time = date.toLocaleTimeString();
return {
date: `${day} ${time}`,
text: message.text,
index: index++
};
});
[].push.apply(this.messages, messages);
}, (err) => {
console.error(err);
this.failed = true;
});
}
}
On the Spring side, if you want it to accept a JWT, you'll need to change to use Jwt instead of OidcUser. Example here.
#GetMapping("/")
public String index(#AuthenticationPrincipal Jwt jwt) {
return String.format("Hello, %s!", jwt.getSubject());
}

Angular2 Spring Authentication

I have separate Angular2 Client and Spring hosted servers.
My Angular2 application is able to call rest call of the spring.
But, I am facing few difficulties to do CSRF authentication with Spring.
main.ts:
import { CsrfBaseRequestOptions } from './app/shared';
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
...
provide(RequestOptions, { useClass: CsrfBaseRequestOptions })
]);
XhrBaseRequestOptions:
#Injectable()
export class XhrBaseRequestOptions extends BaseRequestOptions {
constructor() {
super();
this.headers.append('X-Requested-With', 'XMLHttpRequest');
}
}
CsrfBaseRequestOptions:
import { Injectable } from '#angular/core';
import { XhrBaseRequestOptions } from './xhr-base-request-options';
#Injectable()
export class CsrfBaseRequestOptions extends XhrBaseRequestOptions {
constructor() {
super();
let csrfToken = this.getCsrfToken('X-CSRF-TOKEN');
if (csrfToken) {
this.headers.append('X-CSRF-TOKEN', csrfToken);
}
}
getCsrfToken(tokenName:string):string {
let tokenNameEQ = tokenName + '=';
let ck = document.cookie;
let ca = ck.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(tokenNameEQ) === 0) return c.substring(tokenNameEQ.length, c.length);
}
return null;
}
}
index.ts
onSubmit(event, username, password) {
this.headers = new Headers();
this.headers.append("Content-Type", 'application/json');
let body = JSON.stringify({username, password});
this.http.post('http://localhost:8080/EEP/test', body, { headers: this.headers })
.subscribe((res) => this.token = res.json())
}
hear Nothing is happening.
THanks in advance if you help me to call the Spring ..
I think that you could use the Automatic XSRF handling of Angular2 available from RC2. This is done under the hood by the XSRFHandler class and don't need to add explicitly the cookie to the request.
See these links:
http://5thingsangular.github.io/2016/05/30/issue-6.html
https://github.com/angular/angular/pull/8898

Resources