Thursday, January 20, 2011

Redirect with AuthnRequest

In this post I will show you how to start the SAML Single sign-on process by sending a authentication request using OpenSAML.

The process starts with a redirect from the SP(The one wanting to authenticate someone) to the IdP(The one authenticating).

To start the authentication the SP sends a SAML AuthnRequest as parameter in the redirect.

The AuthnRequest message identifies the SP and can contain information about how the SP wants the user to be authenticated.

Here is an example how to send the AuthnRequest using OpenSAML.

The example uses the HTTPRedirectDeflateEncoder to encode, sign and redirect which makes everything way easier, especially considering encoding and signing.

//IPR Ergogroup AS
public void doAuthenticationRedirect(final HttpServletResponse response, final HttpSession currentSession, final String gotoURL, final SAMLMetaData metaData) throws IllegalArgumentException, SecurityException, IllegalAccessException {
  AuthnRequest authnRequest = generateAuthnRequest(metaData);

  SAMLUtil.logSAMLObject(authnRequest);

  // Save the request ID to session for future validation
  currentSession.setAttribute("AuthnRequestID", authnRequest.getID());
  currentSession.setAttribute("goto", gotoURL);

  HttpServletResponseAdapter responseAdapter = new HttpServletResponseAdapter(response, true);
  BasicSAMLMessageContext<SAMLObject, AuthnRequest, SAMLObject> context = new BasicSAMLMessageContext<SAMLObject, AuthnRequest, SAMLObject>();  
  context.setPeerEntityEndpoint(getEndpointFromMetaData());
  context.setOutboundSAMLMessage(authnRequest);
  context.setOutboundSAMLMessageSigningCredential(getSigningCredential());
  context.setOutboundMessageTransport(responseAdapter);

  HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder();

  try {
   encoder.encode(context);
  } catch (MessageEncodingException e) {
   logger.error(e.getMessage(), e);
  }
 }

 private AuthnRequest generateAuthnRequest(final SAMLMetaData metaData) throws IllegalArgumentException, SecurityException, IllegalAccessException {

  AuthnRequest authnRequest = SAMLUtil.buildSAMLObjectWithDefaultName(AuthnRequest.class);

  authnRequest.setForceAuthn(true);
  authnRequest.setIsPassive(false);
  authnRequest.setIssueInstant(new DateTime());
  for (SingleSignOnService sss : metaData.getIdpEntityDescriptor().getIDPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleSignOnServices()) {
   if (sss.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
    authnRequest.setDestination(sss.getLocation());
   }
  }
  authnRequest.setProtocolBinding(SAMLConstants.SAML2_ARTIFACT_BINDING_URI);

  String deployURL = getDeployURL();
  if (deployURL.charAt(deployURL.length() - 1) == '/') {
   deployURL = deployURL.substring(0, deployURL.length() - 1);
  }
  authnRequest.setAssertionConsumerServiceURL(deployURL + SAMLMetaData.CONSUMER_PATH);

  authnRequest.setID(SAMLUtil.getSecureRandomIdentifier());

  Issuer issuer = SAMLUtil.buildSAMLObjectWithDefaultName(Issuer.class);
  issuer.setValue(getSPEntityId());
  authnRequest.setIssuer(issuer);

  NameIDPolicy nameIDPolicy = SAMLUtil.buildSAMLObjectWithDefaultName(NameIDPolicy.class);
  nameIDPolicy.setSPNameQualifier(getSPEntityId());
  nameIDPolicy.setAllowCreate(true);
  nameIDPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient");

  authnRequest.setNameIDPolicy(nameIDPolicy);

  RequestedAuthnContext requestedAuthnContext = SAMLUtil.buildSAMLObjectWithDefaultName(RequestedAuthnContext.class);
  requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM);

  AuthnContextClassRef authnContextClassRef = SAMLUtil.buildSAMLObjectWithDefaultName(AuthnContextClassRef.class);
  authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");

  requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
  authnRequest.setRequestedAuthnContext(requestedAuthnContext);

  return authnRequest;
 }

Resulting AuthnRequest

my-alias


urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport



The signature is put in the URL with the request.

Further reading

My book, A Guide to OpenSAML, covers AuthnRequests and the rest of the SAML authentication process in detail. It also covers encryption, digital signatures, SAML in general and more.

A Guide to OpenSAML

39 comments:

  1. Interesting stuff but what is the SAMLUtil and SAMLMetaData classes? If they are yours can you post them?

    ReplyDelete
  2. The SAMLMetaData is just a class I use to store my EntityDecriptor for the IDP. It also contains a constant pointing to the URL of my consumer servlet. The SAMLUtil is a collection of static utility methods. I will explain som convieniex methods in post later today

    ReplyDelete
  3. http://mylifewithjava.blogspot.com/2011/04/convenience-methods-for-opensaml.html

    ReplyDelete
    Replies
    1. Hi can u please share Samlutil class

      Delete
    2. The methods are here http://mylifewithjava.blogspot.no/2011/04/convenience-methods-for-opensaml.html

      Delete
    3. Not all of them are there. Do you have a GitHub or something, where you place your code? Also, the code is all messed up around the generic types, your editor must have confused it with HTML tags

      Delete
  4. What do you mean when you say signature is put in URL with the request?
    We have a SP initiated Web SSO and a spring mvc application on the server side,we create authrequest and set it in the request parameter as SAMLRequest. We then a do forward to a jsp page which has a form that submits the form to the IDP as the HTTP POST.My question is does HTTPRedirectDeflateEncoder allows to just get the encoded,signed authn request? My challenge has been to sign the authnrequest? Any inputs are highly appreciated.

    ReplyDelete
    Replies
    1. The HTTPRedirectDeflateEncoder sign deflates and redirects the request. I puts both the SAML request and the signature in the URL as URL parameters. Not sure what your asking. Do you want to sign is without redirecting it?

      Delete
  5. Where do you specify the relaystate?

    ReplyDelete
  6. How final redirect URI looks like?
    Can you provide more details about what does the code below does.

    HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder();
    encoder.encode(context);

    ReplyDelete
    Replies
    1. It encodes the context as an URL. In this case it uses the PeerEntityEndpoint as host in the URL, uses DEFLATE to encode the OutboundSAMLMessage and puts it in a URL paramter, signs the samlmessage with OutboundSAMLMessageSigningCredential and puts it as a URL parameter.

      Delete
  7. Hi,

    I tried to implement the same on a SAML Authn Reques but the encode method is returning NullPointerException on removeSignature method call. The stacktrace is as below

    I am not signing the authn request so how can I avoid calling this method context.setOutboundSAMLMessageSigningCredential(getSigningCredential());

    Thanks in advance
    Rajesh

    java.lang.NullPointerException
    [5/22/13 15:30:49:521 IST] 00000023 SystemErr R at org.opensaml.saml2.binding.encoding.HTTPRedirectDeflateEncoder.removeSignature(HTTPRedirectDeflateEncoder.java:119)
    [5/22/13 15:30:49:521 IST] 00000023 SystemErr R at org.opensaml.saml2.binding.encoding.HTTPRedirectDeflateEncoder.doEncode(HTTPRedirectDeflateEncoder.java:99)
    [5/22/13 15:30:49:521 IST] 00000023 SystemErr R at org.opensaml.ws.message.encoder.BaseMessageEncoder.encode(BaseMessageEncoder.java:51)

    ReplyDelete
    Replies
    1. I dont understand, if you want to avoid to call context.setOutboundSAMLMessageSigningCredential(getSigningCredential()), just dont do it. Delete the line

      Delete
    2. What is the source of getSigningCredential()? Does this come from the Identity Providers metadata file?

      Delete
    3. This is a method I have written myself. It fetches the credential from somewhere. I think in the particular example it gets it from a JKS keystore.

      You can read more on how to fetch the credentails here http://blog.samlsecurity.com/2011/03/getting-credentials-in-opensaml.html

      Delete
  8. Do you have full source code in github?

    ReplyDelete
    Replies
    1. I dont have anything there yet but I do have som tips on where to find examples here http://mylifewithjava.blogspot.no/2012/11/opensaml-sample-code.html

      Delete
    2. I also have on project on github that uses OpenSAML, maybe you can use that, but this is using the IDP functions of OpenSAML https://github.com/rasmusson/MockIDP

      Delete
  9. i tried implementing like the example here and in the book and i keep getting:
    org.opensaml.ws.message.encoder.MessageEncodingException: The signing credential's algorithm URI could not be derived

    when trying to encode
    but when i look at the certificate file, i see that there is a signature algorithm

    any idea what is wrong here?

    ReplyDelete
    Replies
    1. Here is someone with the same problem http://shibboleth.1660669.n2.nabble.com/Sending-Logout-request-from-SP-td7591752.html
      Are you using the private key?

      Delete
    2. Well i tried to add private key and this exception really disappeared.
      But this was with the credentials of my SP.
      The IDP does not know how to handle it. I'm using OKTA IDP for my testing so they gave me a metadata with the certificate, no private key in it. so i'm not sure how i should i handle this, i need to upload some how my SP certificate to the IDP? (a certificate that correspond to the private key i have)

      Delete
    3. Yes this is what you have to do. Normaly you send the IDP your metadata

      Delete
    4. The SAMLRequest is generated without a signature and sigalg parameters
      Do you know what can cause that?
      here:

      https://cteragil.onelogin.com/trust/saml2/http-post/sso/491199?SAMLRequest=fZLLbsIwEEX3%2FYrIexI7IaRYJCgtRUWiVcSji24qxzHFamKnHgfRfn3NS6IbJG88unPu%2BI5H431TezthQGqVIuJj5AnFdSXVZ4rWq2nvHo2zuxGwpg5bmnd2qxbiuxNgvRxAGOv6HrWCrhFmKcxOcrFezFO0tbYFGgStNpbVPrfCML8RwVkDxbEeHLjImzicVMweZ7h0gtmFPqs2EGIS%2BrXmTn64BjUEyJtNUvSBk3iDh7hPYibikvTvxWBYkYSVwyjGeFg6GUAnZgosUzZFjhT3CHFnRQY0TmiE%2FSSK35FXGG011%2FWDVKend0ZRzUACVawRQC2ny%2FxlTkMf0%2FIkAvq8WhW93GWwYdwi7%2B0SY3iI0QWrgJ6Cu81rz%2BYoO%2BVMj1Oba8JtALtsAmVi%2FxVzxauQRMVThH9bvE1GwTU2uyzz1XFmk0LXkv94U20aZm%2FbHCqy6m2OUmoNUyCFsijIzg7%2FP0j2Bw%3D%3D

      Delete
    5. I need to see your code to help you with this. Please send it to my email

      Delete
  10. Hi,

    one question - is it possible to sent a HTTPS Authentication Requst
    (Kerberos) from a Java Rich Client (no webapp) and then get the
    response Assetion using Open SAML. Basically I need the Java Rich
    Client to act as a SP and as Client in the same time?

    Thanks!

    ReplyDelete
    Replies
    1. If your rich client is built to handle the redirects and present the Kerberos ticket to the IDP, I think you can do this. I have never done it though.

      Delete
    2. This comment has been removed by the author.

      Delete
    3. Thanks!
      Do you have any hint or know any example where I can see how to present the Kerberos ticket to the IDP?

      Delete
    4. I dont, but this will not be handled by the OpenSAML lib. Probably you need some windows library on the client side.

      Delete
    5. I got new Informations, my SAML requets should use:

      IsPassive="false", and
      ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"

      is this possible in OpenSAML?

      Regardsing Kerberos ticket, do you know if this should be embedded in the SAML Authentication request, or the IDP reuest it separately from the SP?

      Delete
    6. Yes that binding and setting passive is supported. I have not have time to read this document but I thing you will find alot of answers for your questions here. http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-kerberos-browser-sso.html
      It is a defined standard profile for kerberos SAML authentication

      Delete
  11. Both this post and your book used HttpServletResponseAdapter for servlet based transportation. If this is not the case, say using Apache Synapse, how should this Transport be constructed?

    ReplyDelete
    Replies
    1. Interesting question. I dont know as I have not worked with it, but I think you would use OpenSAML to construct the SAML message and then construct the redirect with signature using controls in the ESB

      Delete
  12. how can i wait for the result of HTTPRedirectDeflateEncoder object.encode()?

    My problem is that i am in a web service and i don´t know how i will wait for the redirect result (the user authenticates in the IDP page) and after that how i catch the result?

    ReplyDelete
    Replies
    1. You can not wait for it per say. The two request are asynchronous. The best way to "wait for a response" is to set the RelayState parameter in the AuthnRequest. The same parameter and value should then be present when the result is delivered. You could also check the responses for the InResponseTo attribute in the response object, when it is the same as the ID in your AuthnRequest you know its your response.

      However using this approach in a webservice sound strange. Normally you dont have a user on the client side, but a machine that can not sign in to the IDP. The more common way to implement WS with SAML is to have the client receive a SAML assertion from a STS before calling the WS. The WS is then called with an already valid SAML assertion.

      Delete
  13. This comment has been removed by the author.

    ReplyDelete