Authentication / Authorization, SSO + Django

Whats in a login

Image Credit: Learn you some Erlang

pbkdf2_sha256$150000$9WTkDsr2ayDh$v96DLMNBVOEjTkwljKByFILJv8721qVhCjII3PJ64w0=
Algo: pbkdf2_sha256
N: 150000
Salt: 9WTkDsr2ayDh
Hash: v96DLMNBVOEjTkwljKByFILJv8721qVhCjII3PJ64w0=
def check_password(password, encoded, setter=None, preferred='default'):
    preferred = get_hasher(preferred)
    try:
        hasher = identify_hasher(encoded)
    except ValueError:
        # encoded is gibberish or uses a hasher that's no longer installed.
        return False

    hasher_changed = hasher.algorithm != preferred.algorithm
    must_update = hasher_changed or preferred.must_update(encoded)
    is_correct = hasher.verify(password, encoded)

    if not is_correct and not hasher_changed and must_update:
        hasher.harden_runtime(password, encoded)

    if setter and is_correct and must_update:
        setter(password)
    return is_correct
In [1]: from django.conf import settings                                                                                                                                                                             

In [2]: settings.PASSWORD_HASHERS                                                                                                                                                                                    
Out[2]: 
['django.contrib.auth.hashers.PBKDF2PasswordHasher',
 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
 'django.contrib.auth.hashers.Argon2PasswordHasher',
 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher']
django.contrib.auth.hashers.PBKDF2PasswordHasher().verify("sydjangorocks", 
"pbkdf2_sha256$150000$9WTkDsr2ayDh$v96DLMNBVOEjTkwljKByFILJv8721qVhCjII3PJ64w0=")                                                    
True

What are the disadvantages of this scheme?

What are the disadvantages of this scheme?

  • Trusting another party with Passwords
  • How to share sessions within multiple apps in org
  • Performance
SESSION_COOKIE_DOMAIN = ".donut.com"

accounts.donut.com

orders.donut.com

donut.donut.com

3 Django Apps pointing to the same store for Authentication

Point to same session Backend

DB / Cache / Cookie / File

Challenges

  • Non homogeneous environment / Different Languages & Frameworks
  • 100's of subdomains. Engineering teams stepping over each others toes
    • How to roll out an upgrade?
    • Data corruption / More possibility of security breach

Introducing SSO

<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" 
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="blabla" 
Version="2.0" ProviderName="SP test" IssueInstant="2014-07-16T23:52:45Z" 
Destination="http://idp.example.com/SSOService.php" 
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" 
AssertionConsumerServiceURL="http://sp.example.com/demo1/index.php?acs">

  <saml:Issuer>http://sp.example.com/demo1/metadata.php</saml:Issuer>
  <samlp:NameIDPolicy 
  Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
  <samlp:RequestedAuthnContext Comparison="exact">
    <saml:AuthnContextClassRef>
    urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
    </saml:AuthnContextClassRef>
  </samlp:RequestedAuthnContext>

</samlp:AuthnRequest>

Signature information omitted

Plugins like: https://addons.mozilla.org/es/firefox/addon/saml-tracer/

Authentication Request

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
  <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
  <samlp:Status>
    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
  </samlp:Status>
  <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
    <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
    <saml:Subject>
      <saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
      </saml:SubjectConfirmation>
    </saml:Subject>
    <saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
      <saml:AudienceRestriction>
        <saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
      </saml:AudienceRestriction>
    </saml:Conditions>
    <saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
      <saml:AuthnContext>
        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
      </saml:AuthnContext>
    </saml:AuthnStatement>
    <saml:AttributeStatement>
      <saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
        <saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
      </saml:Attribute>
    </saml:AttributeStatement>
  </saml:Assertion>
</samlp:Response>

Authentication Response

Relaying Party Identifier donutshop
Hash Algorithm RSA-SHA-256
Valid Redirect Pattern http://donut.com/*
http://subdomain.donut.com/*
ACS URL http://donut.com/acs/
What claims are needed - Username
- Email Address
- Some XYZ Property
Assertion Consumer Service Logout Redirect Binding URL http://logouturl.donut.com

DEMO

https://github.com/onelogin/python3-saml

def init_saml_auth(req):
    auth = OneLogin_Saml2_Auth(req, custom_base_path=settings.SAML_FOLDER)
    return auth
{
    "strict": false,
    "debug": true,
    "sp": {
        "entityId": "demodjango",
        "assertionConsumerService": {
            "url": "http://donut.com/?acs",
            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
        },
        "singleLogoutService": {
            "url": "http://donut.com/?sls",
            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
        },
        "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
    },
    "idp": {
        "entityId": "idpid",
        "singleSignOnService": {
            "url": "http://accounts.donut.com/auth/realms/donutafficionados/protocol/saml",
            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
        },
        "singleLogoutService": {
            "url": "http://accounts.donut.com/auth/realms/donutafficionados/protocol/saml",
            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
        },
        "x509cert": "MIICpTCCAY0CBgFkb76asfsdfsfQsss......"
    }
}
        request_id = None
        if 'AuthNRequestID' in request.session:
            request_id = request.session['AuthNRequestID']

        auth.process_response(request_id=request_id)
        errors = auth.get_errors()
        not_auth_warn = not auth.is_authenticated()
        if not errors:
            if 'AuthNRequestID' in request.session:
                del request.session['AuthNRequestID']
            request.session['samlUserdata'] = auth.get_attributes()
            request.session['samlNameId'] = auth.get_nameid()
            request.session['samlSessionIndex'] = auth.get_session_index()
            print("Session Expiry {}".format(auth.get_session_expiration()))
            if 'RelayState' in req['post_data'] and OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']:
                return HttpResponseRedirect(auth.redirect_to(req['post_data']['RelayState']))

ACS

if len(request.session['samlUserdata']) > 0:
    attributes = request.session['samlUserdata'].items()

return render(request, 'index.html', {'attributes': attributes})

Items from Session

if len(request.session['samlUserdata']) > 0:
    attributes = request.session['samlUserdata'].items()

return render(request, 'index.html', {'attributes': attributes})

Logout

Easier libraries also exist where this view code is written for you e.g. https://github.com/fangli/django-saml2-auth

class CustomerSSO(models.Model)
    sso_type = models.SmallIntegerField(choices=AUTH_CHOICES)
    sso_virtual_host = models.CharField(max_length=200)
    sso_email_regex = models.CharField(max_length=200)
    sso_json_config = models.JsonField(null=True, blank=True)

Do you have multiple customers, each with their own users?

Features

  • SAML & Open ID Connect
  • Roles and Role Inheritance
  • User Attributes
  • Mappers
  • Templates
  • Custom Auth flows. Plugins or write some java [fun :-)]
  • Google Authenticator
  • Cluster mode for high performance

SETUP

Via Docker
https://github.com/keycloak/keycloak-containers/blob/master/server/README.md

Self Contained Wildfly (formerly JBoss Application)

https://www.keycloak.org/docs/6.0/server_installation/#standalone-configuration

# Plug in DB Credentials
vi …/standalone/configuration/standalone.xml

# Start KC Server
.../bin/standalone.sh

Questions

Bonus Content

Open ID Connect

Why

  • SAML Soap / XML Based. Heavy Protocol
  • Finer Grained Revocation
  • Chattier applications and microservices
/realms/{realm-name}/protocol/openid-connect/token

This is the URL endpoint for obtaining a temporary code in the 
Authorization Code Flow or for obtaining tokens via the Implicit Flow, 
Direct Grants, or Client Grants.

/realms/{realm-name}/protocol/openid-connect/auth

This is the URL endpoint for the Authorization Code Flow to turn a 
temporary code into a token.

/realms/{realm-name}/protocol/openid-connect/logout

This is the URL endpoint for performing logouts.

/realms/{realm-name}/protocol/openid-connect/userinfo

This is the URL endpoint for the User Info service described 
in the OIDC specification.

Play with curl
+
jwt.io

Questions?

SSO

By Iqbal Talaat Bhatti