From the source code sandbox Webserver, refresh tokens was done like this:
RefreshTokenProvider = new AuthenticationTokenProvider
{
OnCreate = CreateRefreshToken,
OnReceive = ReceiveRefreshToken,
}
private void CreateRefreshToken(AuthenticationTokenCreateContext context)
{
context.SetToken(context.SerializeTicket());
}
private void ReceiveRefreshToken(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
This create refresh tokens that have the same lifetime as the access tokens.
What would be appropriate lifetime for a refresh token and what would be the suggested way of telling that to the OAuthAuthorizationServer. Theres no options for it, and I am wondering if I should just change it on the ticket in the context of above createRefreshToken.
What would be appropriate lifetime for a refresh token
Its all dependent on use-case. RefreshToken lifetime can be based on the application requirement. Google oAuth has "Refresh tokens are valid until the user revokes access".
what would be the suggested way of telling that to the OAuthAuthorizationServer.
Yes, you are right for the approach. you can set it to Tiken in the context.
private void CreateRefreshToken(AuthenticationTokenCreateContext context)
{
context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddMonths(2));
context.SetToken(context.SerializeTicket());
}
Related
We're creating a small Blazor app which accesses a 3rd party api which we have no control over. The API is secured using OAuth with a client_id and client_secret on a machine to machine basis. There is no authentication from the user.
Initially we hit the token endpoint with the id and secret and it returns a token with an expiry date and time. Currently we just repeat the initial token request before every call to the api so that the token is always brand new and has never expired.
Is this practice accepted or should I be storing the bearer token and it's expiration date and time somewhere secure on the client to check the expiration before requesting a new token?
I'm conscious that if the app is widely used internally at our business we could start to reach the api usage limits, but also, if we store it locally on the client, could I mistakenly leave us vulnerable to XSS attacks or similar.
I'm looking for best practice guidance really, and, if the advice is to store the token and check against expiry, where and how should I be storing the token securely?
I found a nice solution at:
https://referbruv.com/blog/using-imemorycache-for-token-caching-in-an-aspnet-core-application/
The author gives an excellent demonstration of how to use IMemoryCache for my exact scenario. I implement a Token Service which checks the expiry of the cache item. If it's in date, I just used the cached token. If the token has expired, I fetch a new token from the API.
public class TokenService : ITokenService
{
private readonly IApiAccountService _apiAccountService;
private readonly IMemoryCache _cache;
public TokenService(IMemoryCache cache, IApiAccountService apiAccountService)
{
_cache = cache;
_apiAccountService = apiAccountService;
}
public async Task<string> FetchTokenAsync()
{
string token;
if (!_cache.TryGetValue("Token", out token))
{
var authResult = await _apiAccountService.GetToken();
var options = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds((authResult.expires_at - DateTime.Now).TotalSeconds));
_cache.Set("Token", authResult.access_token, options);
token = authResult.access_token;
}
return token;
}
}
Then, when calling my API I just call the Token Service:
private readonly ITokenService _tokenService;
public ApiService(HttpClient httpClient, IConfiguration configuration, ITokenService tokenService)
{
_httpClient = httpClient;
_configuration = configuration;
_tokenService = tokenService;
}
...
private async Task<List<T>> FetchListAsync<T>(string uri)
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await _tokenService.FetchTokenAsync());
var response = await _httpClient.GetAsync(uri);
response.EnsureSuccessStatusCode();
using var responseStream = await
response.Content.ReadAsStreamAsync();
var reader = new StreamReader(responseStream).ReadToEnd();
var responseObject = JsonConvert.DeserializeObject<List<T>>(reader);
return responseObject;
}
The JHipster OAuth2/OIDC default configuration expects the "groups' to be found in the idToken. Can anyone explain how to read the "groups" from the access token instead?
Here are the changes made to retrieve the user's groups / granted authorities from the access token.
Note that for my case the Access Token (JSON) that the auth code is exchanged for contains an "access_token" field as a peer to the idToken. The "access_token" field is an ID or reference to the actual access token with the user's groups. An extra http request is needed to retrieve that "actual" access token.
For Okta the access token is a JWT similar to the idToken so if for some reason you need to configure Okta to add the groups to the access token instead of the idToken you will find them there.
Solution was based in this Spring doc:
Delegation-based strategy with OAuth2UserService
In your WebSecurityConfigurerAdapter class edit your oauth2Login config:
.oauth2Login().userInfoEndpoint().oidcUserService(this.oidcUserService());
Then create the custom oidcUserService():
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
// The access token will be a reference to the actual token
// ( for Okta this would be the actual JWT access token )
String accessTokenRef = userRequest.getAccessToken().getTokenValue();
// Call the end point to get the actual access_token
// ( httpClient is just a RestTemplate impl w/the required configs )
String[] groups = httpClient.fetchGroups(accessTokenRef);
// Create the GrantedAuthority objs & add to mappedAuthorities set
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
for (String group: groups) {
mappedAuthorities.add(new SimpleGrantedAuthority(group));
}
// Create a copy of oidcUser but use the mappedAuthorities instead
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
return oidcUser;
};
}
If you are using JHipster there will be a GrantedAuthoritiesMapper that will need to be updated to map the authorities passed in directly to your application roles rather than reading them from the idToken. Something like:
#Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
Collection<String> roles = new HashSet();
authorities.forEach(authority -> {
roles.add(authority.getAuthority());
});
List<GrantedAuthority> list = SecurityUtils.mapRolesToGrantedAuthorities(roles);
mappedAuthorities = new HashSet<GrantedAuthority>(list);
return mappedAuthorities;
};
}
There are likely some other ways to do this and I would be happy to hear any advice.
Thanks to the commenters for their help.
Let me explain my use case.
I need to have a spring boot oauth2 client application (not a resource server As we already have a separate resource server). Also I have following requirements:
For each out going request to resource server, we need to send id_token. (Done by customizing resttemplate).
For any request, no matter if it invokes resource server or not, If access token is expired my application must refresh it automatically (without any user intervention like any popup or redirection.).
If refresh_token is also expired, user must be logged out.
Questions:
For point 2 and 3, I have spent many hours reading documents and code and Stack Overflow but was not able to find the solution (or did not understand). So I decided to put all pieces together which I found on many blogs and documents, and come up with my solution. Below is my solution for point 2.
Can we please have a look to below code and suggest if there could be any problem with this approach?
How to solve point 3 I am thinking of extending solution for point 2 but not sure what code I need to write, can anyone guide me?
/**
*
* #author agam
*
*/
#Component
public class ExpiredTokenFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(ExpiredTokenFilter.class);
private Duration accessTokenExpiresSkew = Duration.ofMillis(1000);
private Clock clock = Clock.systemUTC();
#Autowired
private OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
#Autowired
CustomOidcUserService userService;
private DefaultRefreshTokenTokenResponseClient accessTokenResponseClient;
private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory;
private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
public ExpiredTokenFilter() {
super();
this.accessTokenResponseClient = new DefaultRefreshTokenTokenResponseClient();
this.jwtDecoderFactory = new OidcIdTokenDecoderFactory();
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
log.debug("my custom filter called ");
/**
* check if authentication is done.
*/
if (null != SecurityContextHolder.getContext().getAuthentication()) {
OAuth2AuthenticationToken currentUser = (OAuth2AuthenticationToken) SecurityContextHolder.getContext()
.getAuthentication();
OAuth2AuthorizedClient authorizedClient = this.oAuth2AuthorizedClientService
.loadAuthorizedClient(currentUser.getAuthorizedClientRegistrationId(), currentUser.getName());
/**
* Check if token existing token is expired.
*/
if (isExpired(authorizedClient.getAccessToken())) {
/*
* do something to get new access token
*/
log.debug(
"=========================== Token Expired !! going to refresh ================================================");
ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
/*
* Call Auth server token endpoint to refresh token.
*/
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(
clientRegistration, authorizedClient.getAccessToken(), authorizedClient.getRefreshToken());
OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient
.getTokenResponse(refreshTokenGrantRequest);
/*
* Convert id_token to OidcToken.
*/
OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
/*
* Since I have already implemented a custom OidcUserService, reuse existing
* code to get new user.
*/
OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(clientRegistration,
accessTokenResponse.getAccessToken(), idToken, accessTokenResponse.getAdditionalParameters()));
log.debug(
"=========================== Token Refresh Done !! ================================================");
/*
* Print old and new id_token, just in case.
*/
DefaultOidcUser user = (DefaultOidcUser) currentUser.getPrincipal();
log.debug("new id token is " + oidcUser.getIdToken().getTokenValue());
log.debug("old id token was " + user.getIdToken().getTokenValue());
/*
* Create new authentication(OAuth2AuthenticationToken).
*/
OAuth2AuthenticationToken updatedUser = new OAuth2AuthenticationToken(oidcUser,
oidcUser.getAuthorities(), currentUser.getAuthorizedClientRegistrationId());
/*
* Update access_token and refresh_token by saving new authorized client.
*/
OAuth2AuthorizedClient updatedAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration,
currentUser.getName(), accessTokenResponse.getAccessToken(),
accessTokenResponse.getRefreshToken());
this.oAuth2AuthorizedClientService.saveAuthorizedClient(updatedAuthorizedClient, updatedUser);
/*
* Set new authentication in SecurityContextHolder.
*/
SecurityContextHolder.getContext().setAuthentication(updatedUser);
}
}
filterChain.doFilter(request, response);
}
private Boolean isExpired(OAuth2AccessToken oAuth2AccessToken) {
Instant now = this.clock.instant();
Instant expiresAt = oAuth2AccessToken.getExpiresAt();
return now.isAfter(expiresAt.minus(this.accessTokenExpiresSkew));
}
private OidcIdToken createOidcToken(ClientRegistration clientRegistration,
OAuth2AccessTokenResponse accessTokenResponse) {
JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
Jwt jwt;
try {
jwt = jwtDecoder
.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
} catch (JwtException ex) {
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null);
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), ex);
}
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(),
jwt.getClaims());
return idToken;
}
}
I am open for any suggestion to improve my code. Thanks.
There are not enough details to understand your use-case fully. It would be great to understand:
Spring security is rapidly evolving around OAuth2, consider mentioning the version you are using. My answer assumes 5.2+
Are you in servlet (user logged in somehow) or non-servlet (like #Scheduled method) environment
From the limited information and my limited knowledge I have following hints:
Consider using WebClient instead of RestTemplate, this is they way to go for the future. It is reactive but don't be scared. It can be used in "blocking" environment as well, you will not use it's full potential but you can still benefit from its better support for OAuth2
WebClient itself has a ServletOAuth2AuthorizedClientExchangeFilterFunction which does pretty much what you are trying to achieve
When creating ServletOAuth2AuthorizedClientExchangeFilterFunction you pass in AuthorizedClientServiceOAuth2AuthorizedClientManager which is a strategy on how to (re)authenticate client.
Sample configuration may look as follows:
#Bean
public WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService) {
AuthorizedClientServiceOAuth2AuthorizedClientManager manager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
manager.setAuthorizedClientProvider(new DelegatingOAuth2AuthorizedClientProvider(
new RefreshTokenOAuth2AuthorizedClientProvider(),
new ClientCredentialsOAuth2AuthorizedClientProvider()));
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(manager);
oauth2.setDefaultClientRegistrationId("your-client-registratioin-id");
return WebClient.builder()
.filter(oauth2)
.apply(oauth2.oauth2Configuration())
.build();
}
And use it as:
#Autowire
private final WebClient webClient;
...
webClient.get()
.uri("http://localhost:8081/api/message")
.retrieve()
.bodyToMono(String.class)
.map(string -> "Retrieved using password grant: " + string)
.subscribe(log::info);
Hope this helps to move in the right direction! Have fun
I am trying to find a way to revoke Oauth2 JWT Refresh Token with vanilla Spring implementation and JwtTokenStore.
First: can somebody confirm that there is no API similar to /oauth/token that allows me to revoke a refresh token?
I wanted to add a custom API that would delete the refresh token along the folowing lines:
OAuth2RefreshToken oauth2RefreshToken=tokenStore.readRefreshToken(refreshToken);
tokenStore.removeRefreshToken(oauth2RefreshToken);
Now, looking at the JwtTokenStore, I noticed that it uses an ApprovalStore. So I went ahead and provided an InMemoryApprovalStore to my JwtTokenStore. My JwtTokenStore instantiation this look as follows:
#Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123456");
return converter;
}
#Bean
public JwtTokenStore getTokenStore(){
tokenStore= new JwtTokenStore(jwtTokenEnhancer());
tokenStore.setApprovalStore(new InMemoryApprovalStore());
tokenStore.setTokenEnhancer(jwtTokenEnhancer());
return tokenStore;
};
Results: with no InMemoryApprovalStore, I can authenticate users and refresh tokens without problems. However, as soon as I add InMemoryApprovalStore to the token store, I start getting the following error message:
{"error":"invalid_grant","error_description":"Invalid refresh token: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NDUwMjQ2MTcsInVzZXJfbmFtZSI6IjYzZjIyYjZlLWU5MGUtNDFjYS1iYzJlLTBmZTgzNmY3MTQ2NyIsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiXSwianRpIjoiMjgwMDgwNWQtMjk1Zi00ZDQzLWI2NTYtMDNlZWYwMWFkMjg0IiwiY2xpZW50X2lkIjoid2ViLWNsaWVudCIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSIsInRydXN0Il19.BPC0HqLYjWGM0IFjvsUGGKQ9dyIXSXwMhraCVFIxD0U"}
My second question is thus what is the proper way to revoke a refresh token?
Edit: I found the following thread that suggests that ApprovalStore is indeed the way to revoke JWT tokens. I now just need to find out how to use them properly.
First: can somebody confirm that there is no API similar to /oauth/token that allows me to revoke a refresh token?
Confirmed.
You don't need to define JwtTokenStore bean, spring will create it for you using AuthorizationServerEndpointsConfigurer
private TokenStore tokenStore() {
if (tokenStore == null) {
if (accessTokenConverter() instanceof JwtAccessTokenConverter) {
this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());
}
else {
this.tokenStore = new InMemoryTokenStore();
}
}
return this.tokenStore;
}
private ApprovalStore approvalStore() {
if (approvalStore == null && tokenStore() != null && !isApprovalStoreDisabled()) {
TokenApprovalStore tokenApprovalStore = new TokenApprovalStore();
tokenApprovalStore.setTokenStore(tokenStore());
this.approvalStore = tokenApprovalStore;
}
return this.approvalStore;
}
My second question is thus what is the proper way to revoke a refresh token?
revoke the approval for the token, this was used by JwtTokenStore
private void remove(String token) {
if (approvalStore != null) {
OAuth2Authentication auth = readAuthentication(token);
String clientId = auth.getOAuth2Request().getClientId();
Authentication user = auth.getUserAuthentication();
if (user != null) {
Collection<Approval> approvals = new ArrayList<Approval>();
for (String scope : auth.getOAuth2Request().getScope()) {
approvals.add(new Approval(user.getName(), clientId, scope, new Date(), ApprovalStatus.APPROVED));
}
approvalStore.revokeApprovals(approvals);
}
}
}
I got a problem when using SignalR with sliding expiration with Forms Authentication.
Because of the SignalR keep polling from server, user auth never expired...
I've researched a lot... and found an interesting article from http://www.asp.net/signalr/overview/security/introduction-to-security#reconcile
They said:
the user's authentication status may change if your site uses sliding expiration with Forms Authentication, and there is no activity to keep the authentication cookie valid. In that case, the user will be logged out and the user name will no longer match the user name in the connection token.
I need to have user's auth expired if he idle for 20 minutes, but I can't..
any ideas?
Thanks a lot!
I ran into this same issue. I found one method posted on GitHub issues that utilizes an HttpModule to remove the auth cookie at the end of the pipeline. This worked great in my scenario.
https://github.com/SignalR/SignalR/issues/2907
public class SignalRFormsAuthenticationCleanerModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.PreSendRequestHeaders += OnPreSendRequestHeaders;
}
private bool ShouldCleanResponse(string path)
{
path = path.ToLower();
var urlsToClean = new string[] { "/signalr/", "<and any others you require>" };
// Check for a Url match
foreach (var url in urlsToClean)
{
var result = path.IndexOf(url, StringComparison.OrdinalIgnoreCase) > -1;
if (result)
return true;
}
return false;
}
protected void OnPreSendRequestHeaders(object sender, EventArgs e)
{
var httpContext = ((HttpApplication)sender).Context;
if (ShouldCleanResponse(httpContext.Request.Path))
{
// Remove Auth Cookie from response
httpContext.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
return;
}
}
}