I have made a small demo on Authorization Code flow of OAuth2 using Spring Security Cloud with Angular 2 client.
Everything is working fine, i am getting the access token response from server.
However as per Aaron perecki's blog https://aaronparecki.com/oauth-2-simplified/
Single-page apps (or browser-based apps) run entirely in the browser after loading the source code from a web page. Since the entire source code is available to the browser, they cannot maintain the confidentiality of their client secret, so the secret is not used in this case. The flow is exactly the same as the authorization code flow above, but at the last step, the authorization code is exchanged for an access token without using the client secret.
So I don't want to use client secret in getting access tokens from auth-server.
However, I am not able to proceed without sharing client secret to auth server.
The following is my Angular 2 logic to retrieve token
import {Injectable} from '@angular/core';
import {IUser} from './user';
import {Router} from '@angular/router';
import {Http, RequestOptions, Headers, URLSearchParams} from '@angular/http';
@Injectable()
export class AuthService {
currentUser: IUser;
redirectUrl: string;
state: string;
tokenObj: any;
constructor(private router: Router, private http: Http) {
this.state = '43a5';
}
isLoggedIn(): boolean {
return !!this.currentUser;
}
loginAttempt(username: string, password: string): void {
const credentials: IUser = {
username: username,
password: password
};
const params = new URLSearchParams();
params.append('client_id', 'webapp');
params.append('redirect_uri', 'http://localhost:9090/callback');
params.append('scope', 'read');
params.append('grant_type', 'authorization_code');
params.append('state', this.state);
params.append('response_type', 'code');
const headers = new Headers({
'Authorization': 'Basic ' + btoa(username + ':' + password)
});
const options = new RequestOptions({headers: headers});
this.http.post('http://localhost:9090/oauth/authorize', params, options)
.subscribe(
data => {
const authresponse = data.json();
this.tokenObj = this.getTokens(authresponse.code).json();
},
err => console.log(err)
);
}
getTokens(code: string): any {
const params = new URLSearchParams();
params.append('grant_type', 'authorization_code');
params.append('code', code);
params.append('redirect_uri', 'http://localhost:9090/callback');
const headers = new Headers({
'Authorization': 'Basic ' + btoa('webapp:websecret')
});
const options = new RequestOptions({headers: headers});
this.http.post('http://localhost:9090/oauth/token', params, options)
.subscribe(
data => {
return data.json();
},
err => console.log(err)
);
}
logout(): void {
this.currentUser = null;
}
}
Here is my AuthorizationServerConfig class source code
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authManager;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(accessTokenConverter()).authenticationManager(authManager);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("permitAll()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/oauth2?createDatabaseIfNotExist=true");
dataSource.setUsername("root");
dataSource.setPassword("chandra");
return dataSource;
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
}
Source code for WebConfig class
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("password1").roles("USER")
.and().withUser("admin1").password("password1").roles("ADMIN");
auth.eraseCredentials(false);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable()
.httpBasic();
}
}
SpringBootApplication class
@SpringBootApplication
@EnableAuthorizationServer
@RestController
public class SpringMicroservicesOauthServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringMicroservicesOauthServerApplication.class, args);
}
@RequestMapping("callback")
public AuthCodeResponse test(@RequestParam("code") String code, @RequestParam("state") String state) {
return new AuthCodeResponse(code,state);
}
}
AuthCodeResponse POJO
public class AuthCodeResponse {
private String code;
private String state;
public AuthCodeResponse() {
}
public AuthCodeResponse(String code, String state) {
this.code = code;
this.state = state;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}