I am working on a springboot+angular app with Okta authentication but I am getting 401 Unauthorized error. Following is my code on the front end side:
app-routing.module.ts
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { OktaCallbackComponent } from '@okta/okta-angular'; import { OktaAuthGuard } from './app.guard'; import { UserDetailsComponent } from './components/user-details/user-details.component'; const routes: Routes = [ { path: 'home', canActivate: [OktaAuthGuard], component: UserDetailsComponent }, { path: 'def', component: OktaCallbackComponent }, { path: '**', redirectTo: '', pathMatch: 'full' }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
app.component.ts
import { Component } from '@angular/core'; import { OktaAuthService } from '@okta/okta-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'users'; isAuthenticated: boolean; constructor(public oktaAuth: OktaAuthService) { // subscribe to authentication state changes this.oktaAuth.$authenticationState.subscribe( (isAuthenticated: boolean) => this.isAuthenticated = isAuthenticated ); } async ngOnInit() { // get authentication state for immediate use this.isAuthenticated = await this.oktaAuth.isAuthenticated(); } async login() { await this.oktaAuth.signInWithRedirect({ originalUri: '/users' }); } async logout() { await this.oktaAuth.signOut(); } }
app.guard.ts
import { OktaAuthService } from '@okta/okta-angular'; import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; @Injectable({ providedIn: 'root' }) export class OktaAuthGuard implements CanActivate { oktaAuth; authenticated; constructor(private okta: OktaAuthService, private router: Router) { this.oktaAuth = okta; } async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { this.authenticated = await this.okta.isAuthenticated(); console.log('can activate?', this.authenticated); if (this.authenticated) { return true; } // Redirect to login flow. this.okta.signInWithRedirect(); return false; } }
app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; //import { OktaAuthModule, OktaCallbackComponent } from '@okta/okta-angular'; import {OktaAuthModule, OKTA_CONFIG} from '@okta/okta-angular'; import { UserDetailsComponent } from './components/user-details/user-details.component'; import { ReactiveFormsModule } from '@angular/forms'; import { OktaAuthGuard } from './app.guard'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { XSRFTokenInterceptor } from './xsrf-token-interceptor'; import { AuthInterceptor } from './auth.interceptor'; const oktaConfig = { issuer: 'https://dev-xxxxxxxx.okta.com/oauth2/default', redirectUri: window.location.origin + '/users', clientId: 'xxxxxxxxxxxxxxx', pkce: true }; @NgModule({ declarations: [ AppComponent, UserDetailsComponent ], imports: [ BrowserModule, OktaAuthModule, AppRoutingModule, HttpClientModule ], providers: [ OktaAuthGuard, {provide: HTTP_INTERCEPTORS, useClass : XSRFTokenInterceptor, multi: true}, { provide: OKTA_CONFIG, useValue: oktaConfig }, {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true},], bootstrap: [AppComponent] }) export class AppModule { }
auth.interceptor.ts
import { Injectable } from '@angular/core'; import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { Observable, from } from 'rxjs'; import { OktaAuthService } from '@okta/okta-angular'; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private oktaAuth: OktaAuthService) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return from(this.handleAccess(request, next)); } private async handleAccess(request: HttpRequest<any>, next: HttpHandler): Promise<HttpEvent<any>> { const accessToken = await this.oktaAuth.getAccessToken(); if (accessToken) { console.log('token: ' + accessToken); request = request.clone({ setHeaders: { Authorization: 'Bearer ' + accessToken } }); } return next.handle(request).toPromise(); } }
user.service.ts
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { environment } from 'src/environments/environment'; import { map } from 'rxjs/operators'; import { HandleError, HttpErrorHandler } from './http-error-handler.service'; import { catchError } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class UserService { private handleError: HandleError; contactsUrl = environment.contactAPI; constructor(private http: HttpClient, httpErrorHandler: HttpErrorHandler) { this.handleError = httpErrorHandler.createHandleError('HeroesService'); } getUsers() { return this.http.get<any>(this.contactsUrl) .pipe(map(info => { console.log("info: "+info); return info; })); }}
xsrf-token-interceptor.ts
import { Injectable } from '@angular/core'; import {HttpEvent, HttpRequest, HttpHandler,HttpInterceptor, HttpErrorResponse, HttpXsrfTokenExtractor} from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable() export class XSRFTokenInterceptor implements HttpInterceptor { constructor(private tokenExtractor: HttpXsrfTokenExtractor) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req); } }
At the backend:
SecurityDevConfiguartion.java
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Profile; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.web.cors.CorsConfiguration; import java.util.Arrays; @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityDevConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { System.out.println("configure"); http.cors().and().csrf().disable(); try { http.antMatcher("/**") .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .antMatchers("/").permitAll() .anyRequest().authenticated(); }catch(Exception e){ e.printStackTrace(); } http.oauth2ResourceServer(); } @Bean CorsConfiguration corsConfiguration() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowCredentials(true); corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:4200")); corsConfiguration.setAllowedHeaders(Arrays.asList("Origin", "Access-Control, Allow-Origin", "Content-Type", "Accept", "Authorization", "Origin, Accept", "X-Requested-With", "Access-Control-Request-Method", "Access-Control-Request-Header" )); corsConfiguration.setExposedHeaders(Arrays.asList("Origin", "Content-Type", "Accept", "Authorization", "Access-Control-Request-Allow-Origin", "Access-Control-Allow-Credentials")); corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); return corsConfiguration ; } }
UsersController.java
import com.example.users.dao.usersDAO; import com.example.users.model.Users; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.security.Principal; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @RestController @CrossOrigin(origins = "http://localhost:4200") public class UsersController { @Autowired usersDAO usersDAO; @RequestMapping(value = "/getUsers", method = RequestMethod.GET) @ResponseBody public List getUsers(@AuthenticationPrincipal Principal userInfo) throws Exception { System.out.println("userInfo: "+userInfo); List<Users> users = usersDAO.findAll(); List<String> l = new ArrayList(); for(Iterator i=Users.iterator();i.hasNext();){ Users u = (Users)(i.next()); l.add(u.getName()); } return l; } }
I have PKCE enabled, grant type is ‘Authorization’ and authorization server is default. ‘http//localhost:4200’ is mentioned in trusted origins in Okta security.
application.properties has client-id and issuer mentioned.
What am I missing/doing wrong? Any help is appreciated.