How does my Spring Boot client application access a refresh token supplied by e.g. Google in Spring Security 5?
Pretty simple question. The remote authorization server (e.g. Google) sends a refresh token, and I want to use it. What's the best way to persist and retrieve it in Spring Security 5?
It seems this answer, this question, and this exernal link describe an approach no longer compatible since Oauth2 became a first-class citizen in Spring Security 5.
Context:
Refresh tokens allow a client application to continue to access resources after a user's session has expired. Per Google's docs, refresh tokens should be persistent:
The application should store the refresh token for future use and use the access token to access a Google API.
Spring security makes the access token widely available in the form of an OAuth2AuthenticationToken, but the refresh token is not included there.
The refresh token is also not available in the OidcUserService (or a class that overrides it), since public OidcUser loadUser(OidcUserRequest userRequest) does not have access to the refresh token. This is a bummer, since it would be nice to override OidcUserService with a custom class that creates/retrieves a user from their OIDC user details and saves their associated refresh token at the same time.
The OAuth2LoginAuthenticationFilter saves the refresh token in the ClientRegistrationRepository:
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(),
oauth2Authentication.getName(),
authenticationResult.getAccessToken(),
authenticationResult.getRefreshToken());
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
The default implemetation keeps the token in transient memory, which is not suitable for distributed applications or persisting across restarts.
It seems there is a JdbcOauth2AuthorizedClientService, with docs recently added, and a schema that suggests that it could be useful, but no example was provided either of configuring it or of using it to retrieve a refresh token.
So how can a client application persist and access refresh token in Spring Security 5?
JdbcOauth2AuthorizedClientService would indeed be appropriate for your use case. It is very simple to configure.
First, you need to add this table to your database:
CREATE TABLE oauth2_authorized_client (
client_registration_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
access_token_type varchar(100) NOT NULL,
access_token_value blob NOT NULL,
access_token_issued_at timestamp NOT NULL,
access_token_expires_at timestamp NOT NULL,
access_token_scopes varchar(1000) DEFAULT NULL,
refresh_token_value blob DEFAULT NULL,
refresh_token_issued_at timestamp DEFAULT NULL,
created_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (client_registration_id, principal_name)
);
Then, configure the JdbcOauth2AuthorizedClientService bean:
#Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService
(JdbcOperations jdbcOperations, ClientRegistrationRepository clientRegistrationRepository) {
return new JdbcOAuth2AuthorizedClientService(jdbcOperations, clientRegistrationRepository);
}
Please note that the current implementation has a bug which will be solved with spring-security version 5.3.2 that is due in a few days from now.
Related
One thing related to OAuth 2.0 and JWTs that's still a bit confusing is when to use scopes vs. roles.
I think some of the confusion is coming from how role-based authorization works in ASP.NET Core (which is the primary language/framework at my workplace). For example; if I have roles in my JWT as follows
{
"aud": "test",
"iss": "http://localhost:8080/auth/realms/test/",
"iat": 1585192274,
"nbf": 1585192274,
"exp": 1585196174,
"sub": "12345",
"roles": ["Admin", "SuperUser"]
}
I can protect routes quite easily without having to do much e.g:
[ApiController]
[Route("api/v{version:apiVersion}/template/test")]
public class TestController : Controller
{
[HttpGet]
[Authorize(Roles = "Admin")]
public IActionResult Get()
{
return Ok("test");
}
}
I could implement something very similar to the above using scopes with dotnet authorization policies, but I'd just like to know if there's some guidance about if/when to use scope or roles, or is it simply a matter of preference...
I can't find much reference to the roles claim in any of the OAuth/JWT-related RFCs, whereas scopes are mentioned throughout.
The most significant difference between scopes and roles/groups is who determines what the client is allowed to do.
Resource scopes are granted by the resource owner (the user) to an application through the consent screen. For example, the client application can post to my timeline or see my friends list.
User roles and groups are assigned by an administrator of the Azure AD directory. For example, the user can submit expense reports or the user can approve expense reports.
Scopes are typically used when an external application wants to gain access to the user's data via an exposed API. They determine what the client application can do.
Role- or group based access is typically used within an application to determine what a user can do.
To enable my service layer to access the current User Id anytime it needs, I use Thread.CurrentPrincipal.
The service layer is used by two front-end layers, one MVC App and one MVC Web Api used for a Mobile App.
In the web app, I use Forms Authentication and the Principal is set into Application_PostAuthenticateRequest. It works fine.
In the web Api, I use Owin. But I cannot find a way to set that Principal after each request is authenticated with the access token.
I can do it when the user logs in with its credentials by overriding GrantResourceOwnerCredentials into my OAuthAuthorizationServerProvider or when he logs with its refresh token by overriding GrantRefreshToken in the same class.
But where could I assign it for requests automatically authenticated with the access token ?
NB. I know that in my Api Controllers I can access the current User, and it is correctly set, but I don't want to pass it with each call to my service layer.
Thanks.
I found how to set it.
The bearer validation is not done by the OAuthAuthorizationServerProvider. I had to implement a custom OAuthBearerAuthenticationProvider, and override the ValidateIdentity method:
public class MyBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
public override Task ValidateIdentity(OAuthValidateIdentityContext context)
{
Thread.CurrentPrincipal = new Principal(context.Ticket.Identity);
return base.ValidateIdentity(context);
}
}
And plug that provider into my app by using:
OAuthBearerAuthenticationOptions bearerAuthenticationOptions = new OAuthBearerAuthenticationOptions()
{
Provider = new MyBearerAuthenticationProvider()
};
app.UseOAuthBearerAuthentication(bearerAuthenticationOptions);
Unfortunately Thread.CurrentPrincipal is null in my Business Layer. I assume the token validation is done in another thread than the request execution. So I'll have to change my method.
I'm adding SSO support to a spring-based application using the spring-security-saml extension. The idea will be that an IDP can register to use SSO with my application by filling out a form in the UI, specifying their entityId, SSO authentication URL (for SP-initialized login), and public X.509 certificate. This seems to be a common workflow for SaaS applications with SSO support.
I haven't yet been able to find a clean way of implementing this with the spring saml extension, and am wondering if there is one. FilesystemMetadataProvider and HTTPMetadataProvider provide support for loading IDP metadata from an XML file or an HTTP GET, respectively, but I need to instead generate the IDP metadata from the above attributes that are stored in the database.
My current thinking is to use ResourceBackedMetadataProvider and use an implementation of Resource that fetches the metadata attributes from the database for each registered IDP. It would look something like:
class DatabaseBackedResource implements Resource {
MetadataDao metadataDao; // autowired
public InputStream getInputStream() throws ResourceException {
Collection<MetadataPojo> idpMetadata = metadataDao.getMetadata();
return convertMetadataPojosToInputStream(idpMetadata);
}
private InputStream convertMetadataPojosToInputStream(Collection<MetadataPojo> metadata) {
// somehow convert attributes to XMLObject
// then write XMLObject to input stream
// ...
}
// implementations of other methods
// ...
}
where MetadataPojo is simply a wrapper object of the above 3 attributes provided by an IDP. What I'm not sure about is how to generate a valid IDP metadata java object, given some attributes, using the spring saml extension. I see that AbstractMetadataProvider#unmarshallMetadata(InputStream) converts the metadata input stream into an XMLObject, but it is not clear to me how I will convert my collection of MetadataPojos into an XMLObject.
In short, is there a tool in the spring saml extension library to build an IDP metadata xml object given a list of string attributes? Or, taking a step back, is there a better way to implement a MetadataProvider that fetches the metadata information from a database instead of a file or url?
Update:
I implemented the above MetadataProvider implementation and Resource subclass and it's been working like a charm. To generate the IDP metadata documents I used various subclasses of AbstractSAMLObjectBuilder (i.e. EntityDescriptorBuilder, IDPSSODescriptorBuilder, KeyDescriptorBuilder, etc.) and that worked pretty cleanly. It would be nice if the Spring SAML library had an IDPMetadataGenerator class like the MetadataGenerator that exists for SP metadata.
If there was a cleaner way about this, I'd love to hear it.
We have an ASP.NET MVC4 web application and in our QA environment we set up different "sites" as WebApplications on the same website, e.g.
www.mysite.co.uk/WebApp1
www.mysite.co.uk/WebApp2
www.mysite.co.uk/WebApp3
For all our cookies, we ensure that the cookie key contains an ID that ties that cookie to the specific Web Application, so there's no cross contamination.
Now this all works perfectly well the vast majority of the time. However, very occasionally in our DEV environment, we find that the GUEST shopper (not authenticated) can access a controller's Action method that is marked with the [Authorize] attribute.
My guess here is that the browser has been used with multiple TABS, each one pointing to a different Web Application, and occasionally the browser/server is getting confused over which ASPXAUTH cookie to use, and is using one from a different Web Application for a shopper who has authenticated. As I said, that's only a guess, but by debugging the site we're definitely hitting a break-point in the code that's supposedly protected with this Attribute.
It's not clear at this point how I may prevent this behaviour.
Thanks
Griff
For all our cookies, we ensure that the cookie key contains an ID that ties that cookie to the specific Web Application, so there's no cross contamination
You are storing the ID within custom cookies, but it appears you are not storing this within the auth cookie that ASP.NET uses to grant access to code marked with the [Authorize] attribute.
You can either add the ID to the encrypted token that gets stored inside the auth cookie and check this per request, or you could encrypt the token with a different key per site.
Fortunately the FormsAuthenticationTicket constructor includes a userdata parameter that can be used for custom data.
public FormsAuthenticationTicket(
int version,
string name,
DateTime issueDate,
DateTime expiration,
bool isPersistent,
string userData
)
You can follow this guide in order to easily store multiple pieces of information using the JSON format like so:-
public static class HttpResponseBaseExtensions
{
public static int SetAuthCookie<T>(this HttpResponseBase responseBase, string name, bool rememberMe, T userData)
{
/// In order to pickup the settings from config, we create a default cookie and use its values to create a
/// new one.
var cookie = FormsAuthentication.GetAuthCookie(name, rememberMe);
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration,
ticket.IsPersistent, userData.ToJson(), ticket.CookiePath);
var encTicket = FormsAuthentication.Encrypt(newTicket);
/// Use existing cookie. Could create new one but would have to copy settings over...
cookie.Value = encTicket;
responseBase.Cookies.Add(cookie);
return encTicket.Length;
}
}
You should also have a custom name for this cookie per site so they can co-exist within your DEV environment.
I would like to add simple authentication to Data Services, for now only to restrict access for particular applications by simple token.
I don't need Domain Authentication or Forms authentication.
I read a lot about authentication here:
http://franssenden.wordpress.com/2010/06/14/custom-security-odata-service-wcf-data-services/
http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2008/06/03/10482.aspx
http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2008/01/15/10119.aspx
http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2008/01/10/10100.aspx
Unfortunately it all demands a loooot of work.
Most of all creating custom IHttpModule.
There should be more simple solution.
I know that when I create object context on the client (WPF) I can add Credentials.
Uri uri = new Uri("http://localhost/myapp/odata.svc");
MyEntities ent= new MyEntities (uri);
ent.Credentials = new NetworkCredential("token", "zx5as9vxc5sa9h0vb6523cv56");
But where can I read them (without implementation of custom IHttpModule)?
I thought that I can use something in class that is implementation of Data Service for example:
protected override void OnStartProcessingRequest(ProcessRequestArgs args)
{
string cred = args.OperationContext.AbsoluteRequestUri.UserInfo;
}
I'm not familiar with UserInfo but description for it stands "Gets the user name, password, ...)
So I have two main questions:
Where can I read Credentials included by typing ent.Credentials = new NetworkCredential("token", "zx5as9vxc5sa9h0vb6523cv56");
Where can I (if I can) set UserInfo on the client app and use it in OnStartProcessingRequest method.
Regards,
Daniel SkowroĊski
There's a series of post about authentication and WCF Data Services (which is the .NET implementation of the OData protocol): http://blogs.msdn.com/b/astoriateam/archive/tags/authentication/
You should be able to find lot more information there (including code samples).