Spring REST API + OAuth2 + Angular

1. Overview

In this tutorial, we’ll secure a REST API with OAuth and consume it from
a simple Angular client.

The application we’re going to build out will consist of four separate
modules:

  • Authorization Server

  • Resource Server

  • UI implicit – a front end app using the Implicit Flow

  • UI password – a front end app using the Password Flow

Further reading:

Using JWT with Spring Security OAuth

A guide to using JSON Web Tokens with both symmetric and asymmetric
signing in Spring Security OAuth.

Read more

OAuth2.0 and Dynamic Client Registration

Learn how to define clients dynamically with Spring Security and OAuth2.

Read
more

2. The Authorization Server

First, let’s start setting up an Authorization Server as a simple Spring
Boot application.

2.1. Maven Configuration

We’ll set up the following set of dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

Note that we’re using spring-jdbc and MySQL because we’re going to use a
JDBC backed implementation of the token store.

2.2. @EnableAuthorizationServer

Now, let’s start configuring the authorization server responsible for
managing access tokens:

@Configuration
@EnableAuthorizationServer
public class AuthServerOAuth2Config
  extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(
      AuthorizationServerSecurityConfigurer oauthServer)
      throws Exception {
        oauthServer
          .tokenKeyAccess("permitAll()")
          .checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
      throws Exception {
        clients.jdbc(dataSource())
          .withClient("sampleClientId")
          .authorizedGrantTypes("implicit")
          .scopes("read")
          .autoApprove(true)
          .and()
          .withClient("clientIdPassword")
          .secret("secret")
          .authorizedGrantTypes(
            "password","authorization_code", "refresh_token")
          .scopes("read");
    }

    @Override
    public void configure(
      AuthorizationServerEndpointsConfigurer endpoints)
      throws Exception {

        endpoints
          .tokenStore(tokenStore())
          .authenticationManager(authenticationManager);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource());
    }
}

Note that:

  • In order to persist the tokens, we used a JdbcTokenStore

  • We registered a client for the “implicit” grant type

  • We registered another client and authorized the “password“,
    authorization_code” and “refresh_token” grant types

  • In order to use the “password” grant type we need to wire in and use
    the AuthenticationManager bean

2.3. Data Source Configuration

Next, let’s configure our data source to be used by the
JdbcTokenStore:

@Value("classpath:schema.sql")
private Resource schemaScript;

@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
    DataSourceInitializer initializer = new DataSourceInitializer();
    initializer.setDataSource(dataSource);
    initializer.setDatabasePopulator(databasePopulator());
    return initializer;
}

private DatabasePopulator databasePopulator() {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScript(schemaScript);
    return populator;
}

@Bean
public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
    dataSource.setUrl(env.getProperty("jdbc.url"));
    dataSource.setUsername(env.getProperty("jdbc.user"));
    dataSource.setPassword(env.getProperty("jdbc.pass"));
    return dataSource;
}

Note that, as we are using JdbcTokenStore we need to initialize
database schema, so we used DataSourceInitializer – and the following
SQL schema:

drop table if exists oauth_client_details;
create table oauth_client_details (
  client_id VARCHAR(255) PRIMARY KEY,
  resource_ids VARCHAR(255),
  client_secret VARCHAR(255),
  scope VARCHAR(255),
  authorized_grant_types VARCHAR(255),
  web_server_redirect_uri VARCHAR(255),
  authorities VARCHAR(255),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(255)
);

drop table if exists oauth_client_token;
create table oauth_client_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication_id VARCHAR(255) PRIMARY KEY,
  user_name VARCHAR(255),
  client_id VARCHAR(255)
);

drop table if exists oauth_access_token;
create table oauth_access_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication_id VARCHAR(255) PRIMARY KEY,
  user_name VARCHAR(255),
  client_id VARCHAR(255),
  authentication LONG VARBINARY,
  refresh_token VARCHAR(255)
);

drop table if exists oauth_refresh_token;
create table oauth_refresh_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication LONG VARBINARY
);

drop table if exists oauth_code;
create table oauth_code (
  code VARCHAR(255), authentication LONG VARBINARY
);

drop table if exists oauth_approvals;
create table oauth_approvals (
    userId VARCHAR(255),
    clientId VARCHAR(255),
    scope VARCHAR(255),
    status VARCHAR(10),
    expiresAt TIMESTAMP,
    lastModifiedAt TIMESTAMP
);

drop table if exists ClientDetails;
create table ClientDetails (
  appId VARCHAR(255) PRIMARY KEY,
  resourceIds VARCHAR(255),
  appSecret VARCHAR(255),
  scope VARCHAR(255),
  grantTypes VARCHAR(255),
  redirectUrl VARCHAR(255),
  authorities VARCHAR(255),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(255)
);

Note that we don’t necessarily need the explicit DatabasePopulator
bean – we could simply use a schema.sql – which Spring Boot makes use
of by default
.

2.4. Security Configuration

Finally, let’s secure the Authorization Server.

When the client application needs to acquire an Access Token, it will do
so after a simple form-login driven auth process:

@Configuration
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth)
      throws Exception {
        auth.inMemoryAuthentication()
          .withUser("john").password("123").roles("USER");
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean()
      throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().permitAll();
    }
}

A quick note here is that the form login configuration isn’t necessary
for the Password flow
– only for the Implicit flow – so you may be able
to skip it depending on what OAuth2 flow you’re using.

*3. The Resource Server

*

Now, let’s discuss the resource server; this is essentially the REST API
which we ultimately want to be able to consume.

3.1. Maven Configuration

Our Resource Server configuration is the same as the previous
Authorization Server application configuration.

3.2. Token Store Configuration

Next, we will configure our TokenStore to access the same database
that authorization server uses to store access tokens:

@Autowired
private Environment env;

@Bean
public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
    dataSource.setUrl(env.getProperty("jdbc.url"));
    dataSource.setUsername(env.getProperty("jdbc.user"));
    dataSource.setPassword(env.getProperty("jdbc.pass"));
    return dataSource;
}

@Bean
public TokenStore tokenStore() {
    return new JdbcTokenStore(dataSource());
}

Note that, for this simple implementation, we’re sharing the SQL backed
token store
even though the Authorization and Resource servers are
separate applications.

The reason, of course, is that the Resource Server needs to be able to
check the validity of the access tokens issued by the Authorization
Server.

3.3. Remote Token Service

Instead of using a TokenStore in our Resource Server, we can use
RemoteTokeServices:

@Primary
@Bean
public RemoteTokenServices tokenService() {
    RemoteTokenServices tokenService = new RemoteTokenServices();
    tokenService.setCheckTokenEndpointUrl(
      "http://localhost:8080/spring-security-oauth-server/oauth/check_token");
    tokenService.setClientId("fooClientIdPassword");
    tokenService.setClientSecret("secret");
    return tokenService;
}

Note that:

  • This RemoteTokenService will use CheckTokenEndPoint on
    Authorization Server to validate AccessToken and obtain Authentication
    object from it.

  • The can be found at AuthorizationServerBaseURL +”/oauth/check_token

  • The Authorization Server can use any TokenStore type
    [JdbcTokenStore, JwtTokenStore, …] – this won’t affect the
    RemoteTokenService or Resource server.

3.4. A Sample Controller

Next, let’s implement a simple controller exposing a Foo resource:

@Controller
public class FooController {

    @PreAuthorize("#oauth2.hasScope('read')")
    @RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
    @ResponseBody
    public Foo findById(@PathVariable long id) {
        return
          new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));
    }
}

Note how the client needs the “read” scope to access this Resource.

We also need to enable global method security and configure
MethodSecurityExpressionHandler:

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig
  extends GlobalMethodSecurityConfiguration {

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        return new OAuth2MethodSecurityExpressionHandler();
    }
}

And here’s our basic Foo Resource:

public class Foo {
    private long id;
    private String name;
}

3.5. Web Configuration

Finally, let’s set up a very basic web configuration for the API:

@Configuration
@EnableWebMvc
@ComponentScan({ "org.baeldung.web.controller" })
public class ResourceWebConfig implements WebMvcConfigurer {}

4. Front end – Setup

We’re now going to look at a simple front-end Angular implementation for
the client.

First, we’ll use Angular CLI to generate and
manage our front-end modules.

First, we’ll install node and npm
as Angular CLI is an npm tool.

Then, we need to use the
frontend-maven-plugin
to build our Angular project using maven:

<build>
    <plugins>
        <plugin>
            <groupId>com.github.eirslett</groupId>
            <artifactId>frontend-maven-plugin</artifactId>
            <version>1.3</version>
            <configuration>
                <nodeVersion>v6.10.2</nodeVersion>
                <npmVersion>3.10.10</npmVersion>
                <workingDirectory>src/main/resources</workingDirectory>
            </configuration>
            <executions>
                <execution>
                    <id>install node and npm</id>
                    <goals>
                        <goal>install-node-and-npm</goal>
                    </goals>
                </execution>
                <execution>
                    <id>npm install</id>
                    <goals>
                        <goal>npm</goal>
                    </goals>
                </execution>
                <execution>
                    <id>npm run build</id>
                    <goals>
                        <goal>npm</goal>
                    </goals>
                    <configuration>
                        <arguments>run build</arguments>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

And finally, generate a new Module using Angular CLI:

ng new oauthApp

Note that we’ll have two front-end modules – one for password flow and
the other for implicit flow.

In the following sections, we will discuss the Angular app logic for
each module.

5. Password Flow using Angular

We’re going to be using the OAuth2 Password flow here – which is why
this is just a proof of concept, not a production ready application.
You’ll notice that the client credentials are exposed to the front end –
which is something we’ll address in a future article.

Our use case is simple: once a user provides their credentials, the
front-end client uses them to acquire an Access Token from Authorization
Server.

5.1. App Service

Let’s start with our AppService – located at app.service.ts – which
contains the logic for server interactions:

  • obtainAccessToken(): to obtain Access token given user credentials

  • saveToken(): to save our access token in a cookie using ng2-cookies
    library

  • getResource(): to get a Foo object from server using its ID

  • checkCredentials(): to check if user is logged in or not

  • logout(): to delete access token cookie and log the user out

export class Foo {
  constructor(
    public id: number,
    public name: string) { }
}

@Injectable()
export class AppService {
  constructor(
    private _router: Router, private _http: Http){}

  obtainAccessToken(loginData){
    let params = new URLSearchParams();
    params.append('username',loginData.username);
    params.append('password',loginData.password);
    params.append('grant_type','password');
    params.append('client_id','fooClientIdPassword');
    let headers = new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8',
      'Authorization': 'Basic '+btoa("fooClientIdPassword:secret")});
    let options = new RequestOptions({ headers: headers });

    this._http.post('http://localhost:8081/spring-security-oauth-server/oauth/token',
      params.toString(), options)
      .map(res => res.json())
      .subscribe(
        data => this.saveToken(data),
        err => alert('Invalid Credentials'));
  }

  saveToken(token){
    var expireDate = new Date().getTime() + (1000 * token.expires_in);
    Cookie.set("access_token", token.access_token, expireDate);
    this._router.navigate(['/']);
  }

  getResource(resourceUrl) : Observable<Foo>{
    var headers = new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8',
      'Authorization': 'Bearer '+Cookie.get('access_token')});
    var options = new RequestOptions({ headers: headers });
    return this._http.get(resourceUrl, options)
                   .map((res:Response) => res.json())
                   .catch((error:any) => Observable.throw(error.json().error || 'Server error'));
  }

  checkCredentials(){
    if (!Cookie.check('access_token')){
        this._router.navigate(['/login']);
    }
  }

  logout() {
    Cookie.delete('access_token');
    this._router.navigate(['/login']);
  }
}

Note that:

  • To get an Access Token we send a POST to the “/oauth/token
    endpoint

  • We’re using the client credentials and Basic Auth to hit this endpoint

  • We’re then sending the user credentials along with the client id and
    grant type parameters URL encoded

  • After we obtain the Access Token – we store it in a cookie

The cookie storage is especially important here, because we’re only
using the cookie for storage purposes and not to drive the
authentication process directly. This helps protect against cross-site
request forgery (CSRF) type of attacks and vulnerabilities.

5.2. Login Component

Next, let’s take a look at our LoginComponent which is responsible for
the login form:

@Component({
  selector: 'login-form',
  providers: [AppService],
  template: `<h1>Login</h1>
    <input type="text" [(ngModel)]="loginData.username" />
    <input type="password"  [(ngModel)]="loginData.password"/>
    <button (click)="login()" type="submit">Login</button>`
})
export class LoginComponent {
    public loginData = {username: "", password: ""};

    constructor(private _service:AppService) {}

    login() {
        this._service.obtainAccessToken(this.loginData);
    }

5.3. Home Component

Next, our HomeComponent which is responsible for displaying and
manipulating our Home Page:

@Component({
    selector: 'home-header',
    providers: [AppService],
  template: `<span>Welcome !!</span>
    <a (click)="logout()" href="#">Logout</a>
    <foo-details></foo-details>`
})

export class HomeComponent {
    constructor(
        private _service:AppService){}

    ngOnInit(){
        this._service.checkCredentials();
    }

    logout() {
        this._service.logout();
    }
}

5.4. Foo Component

Finally, our FooComponent to display our Foo details:

@Component({
  selector: 'foo-details',
  providers: [AppService],
  template: `<h1>Foo Details</h1>
    <label>ID</label> <span>{{foo.id}}</span>
    <label>Name</label> <span>{{foo.name}}</span>
    <button (click)="getFoo()" type="submit">New Foo</button>`
})

export class FooComponent {
    public foo = new Foo(1,'sample foo');
    private foosUrl = 'http://localhost:8082/spring-security-oauth-resource/foos/';

    constructor(private _service:AppService) {}

    getFoo(){
        this._service.getResource(this.foosUrl+this.foo.id)
          .subscribe(
            data => this.foo = data,
            error =>  this.foo.name = 'Error');
    }
}

5.5. App Component

Our simple AppComponent to act as the root component:

@Component({
    selector: 'app-root',
    template: `<router-outlet></router-outlet>`
})

export class AppComponent {}

And the *AppModule* where we wrap all our components, services and
routes:

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    LoginComponent,
    FooComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot([
     { path: '', component: HomeComponent },
    { path: 'login', component: LoginComponent }])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

6. Implicit Flow

Next, we’ll focus on the Implicit Flow module.

6.1. App Service

Similarly, we will start with our service, but this time we will use
library
angular-oauth2-oidc
instead of obtaining access token ourselves:

@Injectable()
export class AppService {

  constructor(
    private _router: Router, private _http: Http, private oauthService: OAuthService){
        this.oauthService.loginUrl = 'http://localhost:8081/spring-security-oauth-server/oauth/authorize';
        this.oauthService.redirectUri = 'http://localhost:8086/';
        this.oauthService.clientId = "sampleClientId";
        this.oauthService.scope = "read write foo bar";
        this.oauthService.setStorage(sessionStorage);
        this.oauthService.tryLogin({});
    }

  obtainAccessToken(){
      this.oauthService.initImplicitFlow();
  }

  getResource(resourceUrl) : Observable<Foo>{
    var headers = new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8',
     'Authorization': 'Bearer '+this.oauthService.getAccessToken()});
    var options = new RequestOptions({ headers: headers });
    return this._http.get(resourceUrl, options)
      .map((res:Response) => res.json())
      .catch((error:any) => Observable.throw(error.json().error || 'Server error'));
  }

  isLoggedIn(){
    if (this.oauthService.getAccessToken() === null){
       return false;
    }
    return true;
  }

  logout() {
      this.oauthService.logOut();
      location.reload();
  }
}

Note how, after obtaining the Access Token, we’re using it via the
Authorization header whenever we consume protected resources from
within the Resource Server.

6.2. Home Component

Our HomeComponent to handle our simple Home Page:

@Component({
    selector: 'home-header',
    providers: [AppService],
  template: `
    <button *ngIf="!isLoggedIn" (click)="login()" type="submit">Login</button>
    <div *ngIf="isLoggedIn">
        <span>Welcome !!</span>
        <a (click)="logout()" href="#">Logout</a>
        <br/>
        <foo-details></foo-details>
    </div>`
})

export class HomeComponent {
    public isLoggedIn = false;

    constructor(
        private _service:AppService){}

    ngOnInit(){
        this.isLoggedIn = this._service.isLoggedIn();
    }

    login() {
        this._service.obtainAccessToken();
    }

    logout() {
        this._service.logout();
    }
}

6.3. Foo Component

Our FooComponent is exactly the same as in the password flow module.

6.4. App Module

Finally, our AppModule:

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    FooComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    OAuthModule.forRoot(),
    RouterModule.forRoot([
     { path: '', component: HomeComponent }])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

7. Run the Front End

1. To run any of our front-end modules, we need to build the app
first:

mvn clean install

2. Then we need to navigate to our Angular app directory:

cd src/main/resources

3. Finally, we will start our app:

npm start

The server will start by default on port 4200, to change port of any
module change the

"start": "ng serve"

in package.json to make it run on port 8086 for example:

"start": "ng serve --port 8086"

8. Conclusion

In this article, we learned how to authorize our application using
OAuth2.

The full implementation of this tutorial can be found in
the GitHub project.

Leave a Reply

Your email address will not be published.