NAV Navbar
ruby python
  • Introduction
  • OpenID Connect Libraries
  • Environment Variables
  • Configure OpenID
  • Example
  • Introduction

    Welcome to the docs for Ultra Auth!

    Our platform is based on OpenID Connect in conjunction with Fido UAF 1.0. We allow dynamic client registration on our OpenID Connect Provider, so you can create as many OpenID Clients as you like.

    In these docs, we put together configuration examples of OpenID Connect Clients using Certified OpenID Connect libraries. These code examples can help you build your own OpenID Connect Clients. To get started quickly, checkout these libraries rolled into our sample apps on Github . OpenID Connect is vast and has a lot of options to configure your clients. We made these samples to get you up and running quick with OpenID Connect. These samples are only intended to get you started, so if you want more features you can roll them yourself. Read the whole OpenID Connect 1.0 Spec to gain some insight into your options.

    Ready to integrate your client with our provider? Check out our OpenID Connect Provider Configuration

    TL;DR You can clone, build, deploy, and connect these OpenID Connect sample clients on Github to our OpenID Connect Provider.

    OpenID Connect Libraries

    gem 'openid_connect'
    
    pip install oic
    

    Environment Variables

    OPENID_ISSUER: ENV['OPENID_ISSUER']
    OPENID_HOST: ENV['OPENID_HOST']
    OPENID_CLIENT_ID: ENV['OPENID_CLIENT_ID']
    OPENID_CLIENT_SECRET: ENV['OPENID_CLIENT_SECRET']
    
    from os import environ
    
    OPENID_ISSUER = environ.get('OPENID_ISSUER')
    OPENID_HOST = environ.get('OPENID_HOST')
    OPENID_CLIENT_ID = environ.get('OPENID_CLIENT_ID')
    OPENID_CLIENT_SECRET = environ.get('OPENID_CLIENT_SECRET')
    

    Configure OpenID

    Create OpenID client

      def options
        {
          identifier: ENV['OPENID_CLIENT_ID'],
          secret: ENV['OPENID_CLIENT_SECRET'],
          scopes_supported: [:openid, :email, :profile, :address],
          host: ENV['OPENID_HOST'],
          port: ENV['OPENID_PORT'],
          scheme: 'https',
          jwks_uri:openid_config.jwks_uri,
          authorization_endpoint: openid_config.authorization_endpoint,
          token_endpoint: openid_config.token_endpoint,
          userinfo_endpoint: openid_config.userinfo_endpoint,
          redirect_uri: openid_auth_callback_url
        }
      end
    
      def client
        @client ||= OpenIDConnect::Client.new options
      end
    
    client = Client(client_authn_method=CLIENT_AUTHN_METHOD)
    provider_info = client.provider_config(OPENID_ISSUER)
    auth_endpoint = provider_info['authorization_endpoint']
    token_endpoint = provider_info['token_endpoint']
    userinfo_endpoint = provider_info['userinfo_endpoint']
    jwks_uri = provider_info['jwks_uri']
    

    Redirect to OpenID authentication page provided by provider

      redirect_to client.authorization_uri(
        response_type: :code,
        state: new_nonce,
        nonce: new_nonce,
        scope: config.scopes_supported & [:openid, :email, :profile, :address].collect(&:to_s)
      )
    
    def auth(request):
        global auth_endpoint
    
        session_info = {}
        session_info['state'] = rndstr()
        session_info['nonce'] = rndstr()
    
        redirect_uri = 'https://{}{}'.format(request.get_host(),
                                             reverse('openid_auth_callback'))
        params = {
            'response_type': 'code',
            'state': session_info['state'],
            'nonce': session_info['nonce'],
            'client_id': OPENID_CLIENT_ID,
            'redirect_uri': redirect_uri,
            'scope': ' '.join(SCOPES_SUPPORTED)
        }
    
        return redirect(auth_endpoint + '?' + urlencode(params))
    

    Get access token

      access_token = client.access_token!(client_auth_method: client_auth_method)
    
      def client_auth_method
        supported = openid_config.token_endpoint_auth_methods_supported
        if supported.present? && !supported.include?('client_secret_basic')
          :post
        else
          :basic
        end
      end
    
    # get access token
    params = {
        'grant_type': 'authorization_code',
        'code': response['code'], # auth code from last request
        'redirect_uri': redirect_uri
    }
    
    auth = (OPENID_CLIENT_ID, OPENID_CLIENT_SECRET)
    access_token_response = requests.post(token_endpoint,
                                          auth=auth,
                                          data=params)
    
    if access_token_response.status_code != 200:
        return HttpResponseBadRequest('Invalid Access Token Response')
    
    access_json = access_token_response.json()
    access_token = access_json['access_token']
    

    It is necessary to request access token after authentication

    Decode token

      _id_token_ = decode_id access_token.id_token
    
      def decode_id(id_token)
        OpenIDConnect::ResponseObject::IdToken.decode id_token, openid_config.jwks
      end
    
    from base64 import b64decode
    
    def b64d(token):
        token += ('=' * (len(token) % 4))
        decoded = b64decode(token)
        return json.loads(decoded)
    

    Verify ID token

      _id_token_.verify!(
        issuer: ENV['OPENID_ISSUER'],
        client_id: ENV['OPENID_CLIENT_ID'],
        nonce: stored_nonce
      )
    
      def stored_nonce
        session.delete(:nonce) or raise SessionBindingRequired.new('Invalid Session')
      end
    
    def verify_id(token):
        global jwks_uri
    
        header, claims, signature = token.split('.')
        header = b64d(header)
        claims = b64d(claims)
    
        if not signature:
            raise ValueError('Invalid Token')
    
        if header['alg'] not in ['HS256', 'RS256']:
            raise ValueError('Unsupported signing method')
    
        if header['alg'] == 'RS256':
            signing_keys = load_jwks_from_url(jwks_uri)
        else:
            signing_keys = [SYMKey(key=str(OPENID_CLIENT_SECRET))]
    
        id_token = JWS().verify_compact(token, signing_keys)
        id_token['header_info'] = header
        return id_token
    

    Example

    Add routes for OpenID

      get '/openid_auth', to: 'openid#openid_auth', as: 'openid_auth'
      get '/openid_auth_callback', to: 'openid#openid_auth_callback', as: 'openid_auth_callback'
      post '/openid_register', to: 'openid#openid_register', as: 'openid_register'
    
    # django urls.py
    
    from django.conf.urls import url
    from django.urls import path
    
    from django.contrib import admin
    admin.autodiscover()
    
    import deauthorized.views
    
    urlpatterns = [
        url(r'^$', deauthorized.views.index, name='index'),
        url(r'^auth', deauthorized.views.auth,
            name='auth'),
        url(r'^openid_auth_callback', deauthorized.views.auth_callback,
            name='openid_auth_callback'),
        path('admin/', admin.site.urls),
    ]