[keycloak-user] How do I get external IDP attributes in custom JS auth flow during broker first login? (I bet Dmitry knows :)

Geoffrey Cleaves geoff at opticks.io
Fri Dec 21 09:52:22 EST 2018


Hi. I was able to successfully maintain the template and edit the error
code using your instructions (thanks again!) but unfortunately bumped into
another snag.

Imagine the scenario where you want to log into Keycloak using your Google
credentials. If you have multiple Google accounts, Google will ask you
which one you want to use. If you accidentally choose the wrong one, my
custom authenticator script will notice that there is no corresponding
email address in Keycloak, deny login and give you the custom message. So
far so good.

The problem arises when the user clicks the Google option again from the
resulting custom error page but on the next attempt selects the correct
Google account. The user should log in normally, but instead I'm getting an
error message: "Unexpected error when authenticating with identity
provider". It's like the session has somehow been poisoned, even though I
added context.clearUser(); context.resetFlow(); to the script.

Here is the full script, in case anybody has any ideas on how to reset the
flow successfully.

function myError(context) {

return context.form().setError("You must have an existing account to log in
via Google.", []).createLogin();

}

AuthenticationFlowError =
Java.type("org.keycloak.authentication.AuthenticationFlowError");

SerializedBrokeredIdentityContext =
Java.type("org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext");
AbstractIdpAuthenticator =
Java.type("org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator");
Response = Java.type("javax.ws.rs.core.Response");
MediaType = Java.type("javax.ws.rs.core.MediaType");
users = session.users().getUsers(realm, false);
//LOG.info("users = " + users);

function authenticate(context) {
    var serializedCtx =
SerializedBrokeredIdentityContext.readFromAuthenticationSession(authenticationSession,
AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
    var biCtx = serializedCtx.deserialize(session, authenticationSession);
    var idpUsername = biCtx.username;
    LOG.info("username = " + idpUsername);
    LOG.info("alias = " + biCtx.idpConfig.alias);

    for(var u in users) {
        //LOG.info("u = " + users[u].getEmail());
        if(idpUsername===users[u].getEmail()) {
            context.success();
            return;
        }
    }

    var response2 = myError(context);
    context.clearUser();
    context.resetFlow();
    context.failure(AuthenticationFlowError.INVALID_CREDENTIALS, response2);
    return;
}


On Mon, 17 Dec 2018 at 04:45, Dmitry Telegin <dt at acutus.pro> wrote:

> Hi Geoffrey, you're welcome :)
>
> As for embedding custom error messages into existing templates, I suggest
> that you check out the following thread:
> http://lists.jboss.org/pipermail/keycloak-user/2018-December/016669.html
>
> Please let me know if it works for you.
>
> Cheers,
> Dmitry
>
> On Fri, 2018-12-14 at 11:49 +0100, Geoffrey Cleaves wrote:
> > Thanks Dmitry, I never in a 1000 years would have figured this out.
> >
> > My goal with all of this is to only allow a user to log in with Google
> (or other provider) if there is already an account created with the same
> email address. My code below works, but instead of returning an entire
> custom page on failure, it would be nice to use the existing template with
> simply a different text. I hate to abuse of your free time, but if you have
> any tips for that I would be most appreciative.
> >
> > AuthenticationFlowError =
> Java.type("org.keycloak.authentication.AuthenticationFlowError");
> >
> > // take a look at org.keycloak.broker.provider.BrokeredIdentityContext
> to figure out what else you can obtain from that object.
> >
> > SerializedBrokeredIdentityContext =
> Java.type("org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext");
> > AbstractIdpAuthenticator =
> Java.type("org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator");
> > Response = Java.type("javax.ws.rs.core.Response");
> > MediaType = Java.type("javax.ws.rs.core.MediaType");
> > response = Response.status(401).entity("<h1>You must have an existing
> account to log in.</h1>").type(MediaType.TEXT_HTML_TYPE).build();
> > users = session.users().getUsers(realm, false);
> >
> > function authenticate(context) {
> >     var serializedCtx =
> SerializedBrokeredIdentityContext.readFromAuthenticationSession(authenticationSession,
> AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
> >     var biCtx = serializedCtx.deserialize(session,
> authenticationSession);
> >     var idpUsername = biCtx.username;
> >     LOG.info("username = " + idpUsername);
> >     LOG.info("alias = " + biCtx.idpConfig.alias);
> >
> >     for(var u in users) {
> >         //LOG.info("u = " + users[u].getEmail());
> >         if(idpUsername===users[u].getEmail()) {
> >             context.success();
> >             return;
> >         }
> >     }
> >
> >     context.failure(AuthenticationFlowError.USER_DISABLED, response);
> >     return;
> > }
> >
> >
> > > On Fri, 14 Dec 2018 at 01:34, Dmitry Telegin <dt at acutus.pro> wrote:
> > > Hello Geoffrey,
> > >
> > > I was right about to click Send when I finally noticed that statement
> in parentheses :-D you were 100% right, what else can I say :)
> > >
> > > Here we go, try this snippet:
> > >
> > > SerializedBrokeredIdentityContext =
> Java.type("org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext");
> > > AbstractIdpAuthenticator =
> Java.type("org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator");
> > >
> > > function authenticate(context) {
> > >
> > >     var serializedCtx =
> SerializedBrokeredIdentityContext.readFromAuthenticationSession(authenticationSession,
> AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
> > >
> > >     var biCtx = serializedCtx.deserialize(session,
> authenticationSession);
> > >
> > >     LOG.info(biCtx.username);
> > >     LOG.info(biCtx.idpConfig.alias);
> > >
> > >     context.success();
> > >
> > > }
> > >
> > > Also take a look at
> org.keycloak.broker.provider.BrokeredIdentityContext to figure out what
> else you can obtain from that object.
> > >
> > > Good luck :)
> > > Dmitry Telegin
> > > CTO, Acutus s.r.o.
> > > Keycloak Consulting and Training
> > >
> > > Pod lipami street 339/52, 130 00 Prague 3, Czech Republic
> > > +42 (022) 888-30-71
> > > E-mail: info at acutus.pro
> > >
> > > On Thu, 2018-12-13 at 14:31 +0100, Geoffrey Cleaves wrote:
> > > > Hello. I have a simple JS execution which denies access as the first
> step
> > > > of the first broker login flow. I would like to access some of the
> > > > attributes that Keycloak writes out to the log when executing this
> flow
> > > > (see below)
> > > >
> > > > What objects or variables must my JS execution load in order to get
> the
> > > > identity_provider_identity attribute listed below?
> > > >
> > > > 20:29:56,588 WARN  [org.keycloak.events] (default task-527)
> > > > type=IDENTITY_PROVIDER_FIRST_LOGIN_ERROR, realmId=re, clientId=tblic,
> > > > userId=null, ipAddress=90., error=user_not_found,
> identity_provider=google,
> > > > auth_method=openid-connect, redirect_uri=
> http://localhost:8222?clientid=tic,
> > > > > > identity_provider_identity=user at gmail.com, code_id=b07317fdb
> > > >
> > > > Thanks in advance!
> > > >
> > > > Geoff
> > > > _______________________________________________
> > > > keycloak-user mailing list
> > > > keycloak-user at lists.jboss.org
> > > > https://lists.jboss.org/mailman/listinfo/keycloak-user
> > >
> >
> >
> > --
> >
> > Regards,
> > Geoffrey Cleaves
> >
> >
> >
> >
> >
>


-- 

Regards,
Geoffrey Cleaves


More information about the keycloak-user mailing list