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
SSO
- 646