When considering how Azure AD works with your app, you can do the following:
The information on OpenID Connnect (OIDC) is relatively substantial, but I felt that the information on SCIM was small, so I started writing an article hoping that there would be settings in Azure AD and an implementation example of the application. However, this time we will cover up to the point where SSO can be done with the first OIDC. The app is implemented in Django and the authentication flow uses the authentication code flow. For the time being, I will set it with an eye on automatic provisioning.
Let's do it! (The source code for this time is on github.)
Django is a Python web application framework. I'm using it because it's easy to move it for the time being. I hope the following explanations will give you a feel for it even if you have no experience with Django, but if you have experience with other MVC frameworks, you may be confused by the Django terminology used in this article. So please be careful about the following.
Django uses the MVC pattern, just like most web frameworks. Reference: About MVC model However, each element is called differently and is called MTV (Model, View, Template). It's confusing, but be aware that ** Django's View is a typical MVC Controller **.
Source: [[Python] Django Tutorial-Making a General Purpose Business Web App Fastest](https://qiita.com/okoppe8/items/54eb105c9c94c0960f14#djnago-%E3%81%AE-mvc%E3%83%91 % E3% 82% BF% E3% 83% BC% E3% 83% B3)
It's one of the most common ways to authenticate and authorize your web app. Details are summarized in the following public information. Again, follow the article below to get a token using the Microsoft Authentication Library (MSAL).
Reference: Microsoft ID Platform and OAuth 2.0 Authentication Code Flow
There are two screens for registering an app in Azure AD, "App Registration" and "Enterprise Application", but you need to register from "Enterprise Application" for automatic provisioning. You need a license of Azure AD Premium P1 or higher, so if you don't have one, use the free trial version.
3.1. Register your app in ** Azure Portal> Azure Active Directory> Enterprise Applications> New Applications> Non-Gallery Applications **.
3.2. In ** Azure Portal> Azure Active Directory> App Registration> All Applications **, find and open the app created above.
3.3. Make a note of the "Application ID" and "Directory ID" on the ** Overview ** page.
3.4. ** Authenticate ** Add the redirect URI from the page.
ʻAccounts` is the name of the app we are making this time.
3.5. ** Certificates and Secrets> Create a client secret with the new client secret ** and make a note of it.
This time I'm using the client secret for simplicity. A certificate can also be used as a more secure method.
Create a mysite
project with the following command:
django-admin startproject mysite
Add a folder to put the Template.
mysite/settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], #add to
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Create an app called ʻaccounts in your
mysite` project with the following command:
python manage.py startapp accounts
accounts/urls.py
from django.urls import include, path
from . import views
urlpatterns = [
path('', views.index),
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'),
path('oidc/callback/', views.callback_view)
]
/ accounts / oidc / callback
is the redirect URI set in step 3.4. After authenticating with Azure AD, the user will be redirected to this URI. It is necessary to implement so that the authentication code passed at that time can be processed by views.callback_view
.
Allows users authenticated with Azure AD to sign in to this app. To do this, you need to associate Azure AD users with app users. This time, we will associate the user whose ObjectId on the Azure AD side and the external_id of the application are the same.
Create a custom user model and add the external_id attribute.
accounts/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
external_id = models.CharField(max_length=50, blank=True)
Modify the administrator screen to show the user's external_id.
accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import User
class UserAdmin(BaseUserAdmin):
list_display = ['username', 'is_staff', 'is_active', 'external_id']
admin.site.register(User, UserAdmin)
Let the custom user model be the authentication user model.
mysite/settings.py
AUTH_USER_MODEL = 'accounts.User' #add to
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'accounts.apps.AccountsConfig', #add to
]
Add ʻaccounts` to the project URL.
mysite/urls.py
from django.contrib import admin
from django.shortcuts import redirect #add to
from django.urls import include, path #include added
urlpatterns = [
path('admin/', admin.site.urls),
path('', lambda req: redirect('accounts/', permanent=False)), #add to
path('accounts/', include('accounts.urls')), #add to
]
Basically, you can authenticate with Azure AD by creating a URL to the Azure AD authentication endpoint and redirecting the user to it. Frequently used features are also available from the library MSAL. The following code is implemented based on Flask + MSAL Sample.
6.1. Add a screen.
6.2. Add settings for authentication and authorization.
mysite/settings.py
# Azure AD
SCOPES = ['User.Read']
TENANT_ID = '{3.3.Directory ID}'
AUTHORITY = f'https://login.microsoftonline.com/{TENANT_ID}/'
CLIENT_ID = '{3.3.Application ID}'
REDIRECT_PATH = '{3.4.Redirect URI}'
CLIENT_SECRET = '{3.5.Client secret}'
ENDPOINT = 'https://graph.microsoft.com/beta/me'
ENDPOINT is an API that is referenced by the authority of the signed-in user. This time, User information acquisition API is executed, so SCOPES
ʻUser.Read` is added to.
6.3. Implement View.
accounts/views.py
import uuid
import msal
from django.shortcuts import redirect
from django.contrib.auth import login, logout
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.contrib.auth import get_user_model
from mysite2 import settings
User = get_user_model()
@login_required
def index(request):
context = {'user': request.user}
return render(request, 'accounts/index.html', context)
def logout_view(request):
logout(request)
return redirect('/')
def login_view(request):
request.session['state'] = str(uuid.uuid4())
auth_url = _build_auth_url(
scopes=settings.SCOPES, state=request.session['state'])
context = {'auth_url': auth_url}
return render(request, 'accounts/login.html', context)
def callback_view(request):
if request.GET.get('state') != request.session.get('state'):
# 'state'Does not match the request
return redirect('/')
if 'error' in request.GET:
#Azure AD authenticates/Returned an authorization error
return render(request, 'accounts/auth_error.html', request.GET)
if 'code' in request.GET:
cache = _load_cache(request)
result = _build_msal_app(cache=cache).acquire_token_by_authorization_code(
request.GET['code'],
scopes=settings.SCOPES, # Misspelled scope would cause an HTTP 400 error here
redirect_uri=settings.REDIRECT_PATH)
if 'error' in result:
return render(request, 'accounts/auth_error.html', result)
request.session['user'] = result.get('id_token_claims')
try:
#Oid on the Azure AD side and external on the app side_Check if the user has the same id
oid = request.session['user']['oid']
user = User.objects.get(external_id=oid)
login(request, user)
except User.DoesNotExist as e:
context = {'error': 'User.DoesNotExist', 'error_description': str(e)}
return render(request, 'accounts/auth_error.html', context)
return redirect('/')
def _build_auth_url(authority=None, scopes=None, state=None):
return _build_msal_app(authority=authority).get_authorization_request_url(
scopes or [],
state=state or str(uuid.uuid4()),
redirect_uri=settings.REDIRECT_PATH)
def _build_msal_app(cache=None, authority=None):
return msal.ConfidentialClientApplication(
settings.CLIENT_ID, authority=settings.AUTHORITY,
client_credential=settings.CLIENT_SECRET, token_cache=cache)
def _load_cache(request):
cache = msal.SerializableTokenCache()
if request.session.get('token_cache'):
cache.deserialize(request.session['token_cache'])
return cache
def _save_cache(request, cache):
if cache.has_state_changed:
request.session['token_cache'] = cache.serialize()
The following are the points.
#Create a DB
python manage.py makemigrations
python manage.py migrate
#Create an admin user
python manage.py createsuperuser
#Start the server
python manage.py runserver
You can access the app by opening http: // localhost: 8000 /
in your browser. / admin
is the admin console.
Now let's sign in to the app as an Azure AD user.
First, assign users to sign in on the ** Azure Portal> Azure Active Directory> Enterprise Applications> App App> Users and Groups ** page.
Next, since we haven't configured auto-provisioning this time, launch the admin shell with python manage.py shell
and manually create the app-side user as shown below.
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.create(username='{Azure AD user username}', external_id='{Azure AD user ObjectId}')
user.save()
Now, when the above user accesses the app and presses the login button, the Azure AD authentication screen will be required. If you enter your credentials there, you will be asked to agree to the app permissions as shown below. This consent screen only requires first-time access. In addition, the administrator can give consent on a tenant-by-tenant basis, in which case the consent screen will not be displayed to general users.
If you accept here, you've successfully signed in!
The app can also access APIs protected by the permissions of the signed-in user.
I was able to sign in to the app as an Azure AD user.
The purpose of having the externalId of the custom user created this time is for authentication and to implement [SCIM API](user provisioning of SCIM in Azure Active Directory (Azure AD)). However, it is quite difficult to implement one by one, so if you have a library for SCIM API, you should use it. This time Django seems to have a library called django-scim2, but it seems that the custom user model also needs to be recreated for the library. If you use the SCIM API, you may need to think from the design stage. I will try to make an app that supports automatic provisioning next time!
Recommended Posts