I configured jhipster with ouath 2.0 that use keycloak as the authorization server. But How I can get the current user's email in my app?
When a user logs in via OAuth2, the account is synchronized to the local user database.
In your application exists SecurityUtils.java which has a method to get the current user's login. You can get the login and query for the user/email.
String login = SecurityUtils.getCurrentUserLogin().orElse("anonymoususer");
Optional<User> optionalUser = userService.getUserWithAuthoritiesByLogin(login);
if (optionalUser.isPresent()) {
String email = optionalUser.get().getEmail();
}
One way is to use the AccountService
In your component, you would do something like shown below. You will need to change the values to match your project
// Add these imports
import { AccountService } from 'app/core/auth/account.service';
import { Account } from 'app/core/auth/account.model';
#Component({
selector: 'jhi-pm-your-project-update',
templateUrl: './pm-your-project-update.component.html',
})
export class YourUpdateComponent implements Oninit{
// Add this varable to store the account information
account: Account | null = null;
constructor(
protected dataUtils: DataUtils,
protected eventManager: EventManager,
protected yourService: YourService,
protected activatedRoute: ActivatedRoute,
protected accountService: AccountService
){}
ngOnInit(): void {
this.accountService.identity().subscribe(account => (this.account = account));
}
save(): void {
// Here is how to access the email
yourEntity.lastModifiedBy = this.account?.email;
}
}
Related
Here's what I currently have:
Spring REST service where many of the APIs require the user to be authenticated
A 'registration' API (/api/v1/register)
A 'login' API that takes username/password (/api/v1/login)
'Facebook Login' API that relies on Spring Social and Spring Security to create a User Connection and log my user in (/auth/facebook)
My problem is that I want these APIs to be used by multiple clients, but the way Facebook Login is right now, it doesn't work well on mobile (works great on a website).
Here's the mobile scenario:
I use Facebook's iOS SDK to request permission from the user
Facebook returns a user access token
I want to send my backend service this token and have Spring Social accept it, create the User Connection, etc.
Can this be done? Or am I going to have to write my own API to persist the User Connection?
Appreciate any help!
I had the exact same issue and here's how I made it work. You probably have a SocialConfigurer somewhere with the following:
#Configuration
#EnableSocial
public class SocialConfig implements SocialConfigurer {
#Autowired
private DataSource dataSource;
#Bean
public FacebookConnectionFactory facebookConnectionFactory() {
FacebookConnectionFactory facebookConnectionFactory = new FacebookConnectionFactory("AppID", "AppSecret");
facebookConnectionFactory.setScope("email");
return facebookConnectionFactory;
}
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
cfConfig.addConnectionFactory(facebookConnectionFactory());
}
#Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
}
// Other #Bean maybe ...
}
From here, what you can do is, in a Controller/RestController, add a mapping with a RequestParam for your token that you will send to your server:
#Autowired
private FacebookConnectionFactory facebookConnectionFactory;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
#RequestMapping(value = "/my-facebook-url", method = RequestMethod.POST)
public String fb(#RequestParam String token) {
AccessGrant accessGrant = new AccessGrant(token);
Connection<Facebook> connection = facebookConnectionFactory.createConnection(accessGrant);
UserProfile userProfile = connection.fetchUserProfile();
usersConnectionRepository.createConnectionRepository(userProfile.getEmail()).addConnection(connection);
// ...
return "Done";
}
Useful references
UsersConnectionRepository
ConnectionRepository
It should be possible to limit Google API OAuth2 requests to a specific google domain. It used to be possible by hacking on the end &hd=mydomain.com to the request. Using the new MVC auth stuff it seems no longer possible. Any ideas how?
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new AppGoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "***.apps.googleusercontent.com",
ClientSecret = "******"
},
Scopes = new[] { DriveService.Scope.Drive },
DataStore = new FileDataStore(HttpContext.Current.Server.MapPath("~/App_Data"), true) ,
});
public override string GetUserId(Controller controller)
{
// In this sample we use the session to store the user identifiers.
// That's not the best practice, because you should have a logic to identify
// a user. You might want to use "OpenID Connect".
// You can read more about the protocol in the following link:
// https://developers.google.com/accounts/docs/OAuth2Login.
var user = controller.Session["user"];
if (user == null)
{
user = Guid.NewGuid();
controller.Session["user"] = user;
}
return user.ToString();
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
public class AppGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
{
public AppGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(String redirectUri)
{
var authorizeUri = new Uri(AuthorizationServerUrl).AddQuery("hd", "ourgoogledomain.com"); //is not in the request
var authUrl = new GoogleAuthorizationCodeRequestUrl(authorizeUri)
{
ClientId = ClientSecrets.ClientId,
Scope = string.Join(" ", Scopes),
RedirectUri = redirectUri,
//AccessType = "offline",
// ApprovalPrompt = "force"
};
return authUrl;
}
}
Passing a hd parameter is indeed the correct way to limit users to your domain. However, it is important that you verify that the user does actually belong to that hosted domain. I see in your answer that you figured out how to add this parameter back in to your request, so I will address the second part of this.
The issue is that the user can actually modify the requested URL in their client and remove the hd parameter! So while it's good to pass this parameter for the best UI for your users, you need to also verify that authenticated users do actually belong to that domain.
To see which hosted Google Apps for Work domain (if any) the user belongs to, you must include email in the list of scopes that you authorize. Then, do one of the following:
Option 1. Verify the ID Token.
When you exchange your code for an access token, the token endpoint will also return an ID Token in the id_token param (assuming you include an identity scope in your request such as email). If the user is part of a hosted domain, a hd claim will be present, you should check that it is present, and matches what you expect.
You can read more about ID tokens on Google's OpenID Connect docs (including some links to sample code and libraries to help you decode them). This tool can decode ID Tokens during testing.
Option 2. Call UserInfo
Once you have the OAuth Access Token, perform a GET request to https://www.googleapis.com/plus/v1/people/me/openIdConnect with the Access Token in the header. It will return a JSON dictionary of claims about the user. If the user is part of a hosted domain, a hd claim will be present, you should check that it is present, and matches what you expect.
Read more in the documentation for Google's UserInfo endpoint.
The main difference between Option 1 and Option 2 is that with the ID Token, you avoid another HTTP round-trip to the server making it faster, and less error-prone. You can try out both these options interactively using the OAuth2 Playground.
With the updated for .NET core package previous answers are no longer suitable. Fortunately in the new implementation there is a way to hook into authentication events to perform such task.
You will need a class that will handle 2 events - the one that fired before you go to Google and the one for when coming back. In first you limit which domain can be used to sign-in and in the second you ensure that the email with the right domain was in fact used for signin:
internal class GoogleAuthEvents : OAuthEvents
{
private string _domainName;
public GoogleAuthEvents(string domainName)
{
this._domainName = domainName?.ToLower() ?? throw new ArgumentNullException(nameof(domainName));
}
public override Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context)
{
return base.RedirectToAuthorizationEndpoint(new OAuthRedirectToAuthorizationContext(
context.HttpContext,
context.Options,
context.Properties,
$"{context.RedirectUri}&hd={_domainName}"));
}
public override Task TicketReceived(TicketReceivedContext context)
{
var emailClaim = context.Ticket.Principal.Claims.FirstOrDefault(
c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
if (emailClaim == null || !emailClaim.Value.ToLower().EndsWith(_domainName))
{
context.Response.StatusCode = 403; // or redirect somewhere
context.HandleResponse();
}
return base.TicketReceived(context);
}
}
and then you need to pass this "events handler" to the middleware via GoogleOptions class:
app.UseGoogleAuthentication(new GoogleOptions
{
Events = new GoogleAuthEvents(Configuration["Authentication:Google:LimitToDomain"])
})
#AMH, to do in simplest way you should create your own Google Provider, override method ApplyRedirect and append additional parameter like hd to address which will be using to redirect to a new google auth page:
public class GoogleAuthProvider : GoogleOAuth2AuthenticationProvider
{
public override void ApplyRedirect(GoogleOAuth2ApplyRedirectContext context)
{
var newRedirectUri = context.RedirectUri;
newRedirectUri += string.Format("&hd={0}", "your_domain.com");
context.Response.Redirect(newRedirectUri);
}
}
After that just link new provider to your options:
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = "your id",
ClientSecret = "your secret",
Provider = new GoogleAuthProvider(),
});
Having downloaded the source, I was able to see it is easy to subclass the request object, and add custom parameters:
public class GoogleDomainAuthorizationCodeRequestUrl : GoogleAuthorizationCodeRequestUrl
{
/// <summary>
/// Gets or sets the hosted domain.
/// When you want to limit authorizing users from a specific domain
/// </summary>
[Google.Apis.Util.RequestParameterAttribute("hd", Google.Apis.Util.RequestParameterType.Query)]
public string Hd { get; set; }
public GoogleDomainAuthorizationCodeRequestUrl(Uri authorizationServerUrl) : base(authorizationServerUrl)
{
}
}
public class AppGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
{
public AppGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(String redirectUri)
{
var authUrl = new GoogleDomainAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
{
Hd = "mydomain.com",
ClientId = ClientSecrets.ClientId,
Scope = string.Join(" ", Scopes),
RedirectUri = redirectUri
};
return authUrl;
}
}
I found this post when searching for a solution to specify the hosted domain with OpenID Connect integration to Google. I was able to get it working using the Google.Apis.Auth.AspNetCore package and the following code.
In Startup.cs
services.AddGoogleOpenIdConnect(options =>
{
options.ClientId = "*****";
options.ClientSecret = "*****";
options.SaveTokens = true;
options.EventsType = typeof(GoogleAuthenticationEvents);
});
services.AddTransient(provider => new GoogleAuthenticationEvents("example.com"));
Don't forget app.UseAuthentication(); in the Configure() method of Startup.cs.
Then the authentication events class
public class GoogleAuthenticationEvents : OpenIdConnectEvents
{
private readonly string _hostedDomain;
public GoogleAuthenticationEvents(string hostedDomain)
{
_hostedDomain = hostedDomain;
}
public override Task RedirectToIdentityProvider(RedirectContext context)
{
context.ProtocolMessage.Parameters.Add("hd", _hostedDomain);
return base.RedirectToIdentityProvider(context);
}
public override Task TicketReceived(TicketReceivedContext context)
{
var email = context.Principal.FindFirstValue(ClaimTypes.Email);
if (email == null || !email.ToLower().EndsWith(_hostedDomain))
{
context.Response.StatusCode = 403;
context.HandleResponse();
}
return base.TicketReceived(context);
}
}
I want to connect with Google calender, Google contacts and Google+ in my grails application. I am able to connect only one google service at a time with available google provider. So I have to add new custom provider.
My code is
package org.scribe.api;
import org.scribe.builder.api.DefaultApi10a;
import org.scribe.model.*;
public class GoogleContactApi extends DefaultApi10a
{
private static final String AUTHORIZATION_URL = "https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token=%s";
#Override
public String getAccessTokenEndpoint()
{
return "https://www.google.com/accounts/OAuthGetAccessToken";
}
#Override
public String getRequestTokenEndpoint()
{
return "https://www.google.com/accounts/OAuthGetRequestToken";
}
#Override
public String getAuthorizationUrl(Token requestToken)
{
return String.format(AUTHORIZATION_URL, requestToken.getToken());
}
}
my configuration
googleContact{
api = org.scribe.builder.api.GoogleApi
key = '1xxxxxxxx'
secret = 'xxxxxxxxxx'
scope = 'https://www.google.com/m8/feeds'
callback = "${grails.serverURL}/oauth/google/callback"
successUri = "${grails.serverURL}/oauthCallBack/googleContact"
}
But I am getting error
Unknown provider googleContact, check your configuration..
Please provide guidance.
No need to create a custom provide to connect google with different google applications.
Just need to give different provider name in the code, e.g.,
Config.groovy
google {
api = org.scribe.builder.api.GoogleApi
key = 'XXX'
secret = 'YYY'
scope = 'https://www.googleapis.com/auth/userinfo.profile'
callback = "${grails.serverURL}/oauth/google/callback"
successUri = "${grails.serverURL}/oauthCallBack/google"
}
googlecontact {
api = org.scribe.builder.api.GoogleApi
key = 'XXX'
secret = 'YYY'
scope = 'https://www.googleapis.com/auth/calendar'
callback = "${grails.serverURL}/oauth/googlecontact/callback"
successUri = "${grails.serverURL}/oauthCallBack/googlecontact"
}
View
<oauth:connect provider="googlecontact">Google Contact</oauth:connect>
<oauth:connect provider="google">Google</oauth:connect>
and OauthCallBackController
def google() {
// your code
}
def googlecontact(){
// your code
}
NOTE: use googlecontact if you use googleContact then you got error.
Is there any way to define the users that can use my application in a list in Config.groovy? This will be using Grails 2.2.3 and the latest versions of Spring Security Core and Spring Security LDAP.
We use Active Directory for authentication, and only 2 or 3 people will use this little application, so it doesn't seem worthy of making an AD Group for just this app. It would be simpler to define a list, and any time there is a new hire instead of adding them to the AD group all I have to do is add their name to the external Grails config.
I would like to do something like the following:
SomeController.groovy
#Secured("authentication.name in grailsApplication.config.my.app.usersList")
class SomeController {
}
Then in Config.groovy put this code:
my.app.usersList = ['Bill', 'Tom', 'Rick']
Is this possible? If so, is this a terrible idea? Thanks a lot.
That seems really silly. Why not have the list of users in a table? Then you can add/remove from that table without have to modify the application.
I currently do this and in my UserDetailsContextMapper I make sure the username already exists in the Users table.
You need a custom authenticator that will try to access your Active Directory and if authenticated, will look into Grails properties to check if the username is allowed to login.
This is the class that I use. I changed the code to validate the config:
class ActiveDirectoryAuthenticator {
private DefaultSpringSecurityContextSource contextFactory
private String principalSuffix = ""
def grailsApplication
public DirContextOperations authenticate(Authentication authentication) {
// Grab the username and password out of the authentication object.
String principal = authentication.getName() + "#" + principalSuffix
String password = ""
if (authentication.getCredentials() != null) {
password = authentication.getCredentials().toString()
}
// If we have a valid username and password, try to authenticate.
if (!("".equals(principal.trim())) && !("".equals(password.trim()))) {
try {
String provider = contextFactory.getUrls()[0]
Hashtable authEnv = new Hashtable(11)
authEnv.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory")
authEnv.put(Context.PROVIDER_URL, provider)
authEnv.put(Context.SECURITY_AUTHENTICATION, "simple")
authEnv.put(Context.SECURITY_PRINCIPAL, principal)
authEnv.put(Context.SECURITY_CREDENTIALS, password)
javax.naming.directory.DirContext authContext = new InitialDirContext(authEnv)
//here validate the user against your config.
if(!authentication.getName() in grailsApplication.config.adUsersAllowed) {
throw new BadCredentialsException("User not allowed.")
}
DirContextOperations authAdapter = new DirContextAdapter()
authAdapter.addAttributeValue("ldapContext", authContext)
return authAdapter
} catch ( NamingException ex ) {
throw new BadCredentialsException(ex.message)
}
} else {
throw new BadCredentialsException("Incorrect username or password")
}
}
public DefaultSpringSecurityContextSource getContextFactory() {
return contextFactory
}
/**
* Set the context factory to use for generating a new LDAP context.
*
* #param contextFactory
*/
public void setContextFactory(DefaultSpringSecurityContextSource contextFactory) {
this.contextFactory = contextFactory
}
public String getPrincipalSuffix() {
return principalSuffix
}
/**
* Set the string to be prepended to all principal names prior to attempting authentication
* against the LDAP server. (For example, if the Active Directory wants the domain-name-plus
* backslash prepended, use this.)
*
* #param principalPrefix
*/
public void setPrincipalSuffix(String principalSuffix) {
if (principalSuffix != null) {
this.principalSuffix = principalSuffix
} else {
this.principalSuffix = ""
}
}
}
Declare it as your ldapAuthenticator in resources.groovy:
ldapAuthenticator(ActiveDirectoryAuthenticator) {
contextFactory = ref('contextSource')
principalSuffix = 'domain.local' //your domain suffix
grailsApplication = ref('grailsApplication')
}
The downside is that you need to restart your context when you change config.groovy
In your controllers just use #Secured('IS_AUTHENTICATED_FULLY')
I do not think you can do that because annotations are resolved at compile time and not in runtime. Config properties will be read during the application runtime so you I fear you have to end up doing:
#Secured(["authentication.name in ['Bill', 'Tom', 'Rick']"])
class SomeController {
}
If I remember correctly the #Secured annotation cannot be used for other things than comparing roles. But you should be able to do this with spring securities #PreAuthorize and #PostAuthorize annotations. When using grails the easiest way to setup these annotations is installing the spring security ACL plugin.
Within #PreAuthorize and #PostAuthorize you can use SPEL expressions which are more flexible. Unfortunatelly SPEL does not provide an in operator. However you can delegate the security check to a service:
#PreAuthorize('#securityService.canAccess(authentication)')
public void test() {
println "test?"
}
With the # symbol you can reference other beans like services within expression. Here the method securityService.canAccess() is called to evaluate if the logged in user can access this method.
To use this you have to configure a BeanResolver. I wrote some more details about configuring a BeanResolver here.
Within securityService you can now do:
class SecurityService {
def grailsApplication
public boolean canAccess(Authentication auth) {
return grailsApplication.config.myList.contains(auth.name)
}
}
In general I would not recommend to use a configuration value for validating the user in security checks. The groovy configuration will be compiled so you cannot easily add a new user without redeploying your application.
I've been struggling with Spring Security LDAP for a while now, and just when I finally thought I had it beaten into submission, its tripped me up again.
The scenario: I'm having users log in using their organisation credentials, which I then check against the internal user database. If they don't exist, I create them locally and then set their local authorities (I can't touch the LDAP server, only authenticate against it - so I can't add the authorities there). This works fine... but I then want to add a couple of fields from LDAP - their full name and email address, and this is where it goes wrong.
I have LDAP searches working in a controller - using the following code (I've injected ldapTemplate further up):
def fullName = ldapTemplate.search("", "(uid=" + uid + "*)", new AttributesMapper() {
#Override
public Object mapFromAttributes(Attributes attrs) throws NamingException {
return attrs.get("cn").get()
}
})
and this works perfectly. But when I replicate this code in a service that is called post-authentication the ldapTemplate is always null... I've tried a few different things, but I can't get it to populate properly... can anyone shed some light on why? I'm hoping its something stupid I'm doing since its 4:21am and I should have been in bed about 6 hours ago...
EDIT: Here is the service code as requested, thanks for taking a look Burt - its currently not very robust as I haven't got it working past simply creating a new local user - there is work to do on it yet.
package com.myPackage
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
import org.springframework.ldap.core.AttributesMapper
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.GrantedAuthorityImpl
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler
import org.apache.commons.lang.RandomStringUtils
class PostAuthHandlerService implements AuthenticationSuccessHandler {
def springSecurityService
def ldapTemplate
private AuthenticationSuccessHandler target = new SavedRequestAwareAuthenticationSuccessHandler();
private List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication auth) {
def username = auth.principal.getAt("username")
User.withTransaction { status ->
// Check to see if this user exists in the local db
def user = User.findByUsername(username)
if (!user) { // User doesn't exist yet, so create and make a user...
def userRole = Role.findByAuthority('ROLE_USER') ?:
new Role(authority: 'ROLE_AM').save(failOnError: true)
user = new User(
username: username,
password: auth.getCredentials() ?: placeholderPassword(),
displayName: getLdapDetails("username", "cn") ?: "",
email: getLdapDetails("username", "mail") ?: "",
enabled: true).save(failOnError: true)
UserRole.create user, userRole
}
else {
println("--- You exist already! Updating details")
user.displayName = getLdapDetails("username", "cn") ?: ""
}
target.onAuthenticationSuccess(request, response, auth)
}
}
def getLdapDetails(username, attr) {
return ldapTemplate.search("", "(uid=$username*)", new AttributesMapper() {
#Override
public Object mapFromAttributes(Attributes attrs) throws NamingException {
return attrs.get(attr).get()
}
})
}
def placeholderPassword () {
// This is here because we also allow local logins for some users.
// If the password is not available, then create a big ugly one...
return org.apache.commons.lang.RandomStringUtils.randomAlphanumeric(128)
}
public void proceed(HttpServletRequest request,
HttpServletResponse response, Authentication auth) {
target.onAuthenticationSuccess(request, response, auth)
}
}
2nd Edit: I've been trying various different things - including trying to use springSecurityService.reauthenticate to get the local user to be used during the session rather than the LDAP one - and this bean is null as well... it seems like I can't inject any beans into my service at all.
I eventually found this post: http://burtbeckwith.com/blog/?p=993 which gave me a workaround and I now have it working... but I'd still like to know why it didn't in the first place... I have so much to learn in Grails - and its hard to know where to start.
Cheers in advance
Steve
How are you injecting authenticationSuccessHandler ? You should try defining authenticationSuccessHandler in resources.xml
Grails 1.3.5 and Spring Security Core