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.